Changing reload mechanism and adding UIList

Added support for MP4, STL, and VOX
Created new __init__.py file
Created get_combinations.py file
Created __init__.py file in ui_Lists folder

Added UIList.py for tests
pull/54/head
Torrin Leonard 2022-02-02 19:40:05 -05:00
rodzic 0a06052824
commit ea84bf23c0
6 zmienionych plików z 744 dodań i 32 usunięć

Wyświetl plik

@ -11,19 +11,35 @@ bl_info = {
# Import handling:
import bpy
from bpy.app.handlers import persistent
import os
import importlib
from .main import DNA_Generator, Batch_Sorter, Exporter, Batch_Refactorer
# Import files from main directory:
importlib.reload(DNA_Generator)
importlib.reload(Batch_Sorter)
importlib.reload(Exporter)
importlib.reload(Batch_Refactorer)
importList = ['DNA_Generator', 'Batch_Sorter', 'Exporter', 'Batch_Refactorer', 'get_combinations', 'UIList']
if bpy in locals():
importlib.reload(DNA_Generator)
importlib.reload(Batch_Sorter)
importlib.reload(Exporter)
importlib.reload(Batch_Refactorer)
importlib.reload(get_combinations)
importlib.reload(UIList)
else:
from .main import \
DNA_Generator, \
Batch_Sorter, \
Exporter, \
Batch_Refactorer, \
get_combinations
from .ui_Lists import UIList
# User input Property Group:
class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
# Main BMNFTS Panel properties:
@ -56,9 +72,10 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
name="Animation File Format",
description="Select Animation file format",
items=[
('AVI_JPEG', "AVI_JPEG", "Export NFT as AVI_JPEG"),
('AVI_RAW', "AVI_RAW", "Export NFT as AVI_RAW"),
('FFMPEG', "FFMPEG", "Export NFT as FFMPEG")
('AVI_JPEG', '.avi (AVI_JPEG)', 'Export NFT as AVI_JPEG'),
('AVI_RAW', '.avi (AVI_RAW)', 'Export NFT as AVI_RAW'),
('FFMPEG', '.mkv (FFMPEG)', 'Export NFT as FFMPEG'),
('MP4', '.mp4', 'Export NFT as .mp4')
]
)
@ -73,7 +90,8 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
('FBX', '.fbx', 'Export NFT as .fbx'),
('OBJ', '.obj', 'Export NFT as .obj'),
('X3D', '.x3d', 'Export NFT as .x3d'),
('VOX', '.vox', 'Export NFT as .vox, requires the voxwriter add on: https://github.com/Spyduck/voxwriter')
('STL', '.stl', 'Export NFT as .stl'),
('VOX', '.vox (Experimental)', 'Export NFT as .vox, requires the voxwriter add on: https://github.com/Spyduck/voxwriter')
]
)
@ -86,7 +104,6 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
# API Panel properties:
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD')
def make_directories(save_path):
Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data")
batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data")
@ -101,6 +118,21 @@ def make_directories(save_path):
os.makedirs(nftBatch_save_path)
return Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path
# Update NFT count:
combinations: int = 0
@persistent
def update_combinations(dummy1, dummy2):
global combinations
combinations = get_combinations.get_combinations_from_scene()
redraw_panel()
print(combinations)
bpy.app.handlers.depsgraph_update_post.append(update_combinations)
# Main Operators:
class createData(bpy.types.Operator):
bl_idname = 'create.data'
bl_label = 'Create Data'
@ -171,8 +203,6 @@ class refactor_Batches(bpy.types.Operator):
Batch_Refactorer.reformatNFTCollection(save_path, Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path,
cardanoMetaDataBool, solanaMetaDataBool, erc721MetaData)
# Main Panel:
class BMNFTS_PT_MainPanel(bpy.types.Panel):
bl_label = "Blend_My_NFTs"
@ -186,6 +216,8 @@ class BMNFTS_PT_MainPanel(bpy.types.Panel):
scene = context.scene
mytool = scene.my_tool
layout.label(text=f"Maximum Number Of NFTs: {combinations}")
row = layout.row()
row.prop(mytool, "nftName")
@ -237,26 +269,84 @@ class BMNFTS_PT_MainPanel(bpy.types.Panel):
row.prop(mytool, "erc721MetaData")
self.layout.operator("refactor.batches", icon='MESH_CUBE', text="Refactor Batches & create MetaData")
# API Panel:
class BMNFTS_PT_API_Panel(bpy.types.Panel):
bl_label = "API"
bl_idname = "BMNFTS_PT_API_Panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Blend_My_NFTs'
def draw(self, context):
layout = self.layout
scene = context.scene
mytool = scene.my_tool
row = layout.row()
row.prop(mytool, "apiKey")
row.operator("wm.url_open", text="Documentation", icon='URL').url = "https://github.com/torrinworx/Blend_My_NFTs"
# # Logic Panel:
# class BMNFTS_PT_LOGIC_Panel(bpy.types.Panel):
# bl_label = "Logic"
# bl_idname = "BMNFTS_PT_LOGIC_Panel"
# bl_space_type = 'VIEW_3D'
# bl_region_type = 'UI'
# bl_category = 'Blend_My_NFTs'
#
# def draw(self, context):
# layout = self.layout
# scene = context.scene
# mytool = scene.my_tool
#
# # Materials Panel:
#
# class BMNFTS_PT_MATERIALS_Panel(bpy.types.Panel):
# bl_label = "Materials"
# bl_idname = "BMNFTS_PT_MATERIALS_Panel"
# bl_space_type = 'VIEW_3D'
# bl_region_type = 'UI'
# bl_category = 'Blend_My_NFTs'
#
# def draw(self, context):
# layout = self.layout
# scene = context.scene
# mytool = scene.my_tool
#
# # API Panel:
# class BMNFTS_PT_API_Panel(bpy.types.Panel):
# bl_label = "API"
# bl_idname = "BMNFTS_PT_API_Panel"
# bl_space_type = 'VIEW_3D'
# bl_region_type = 'UI'
# bl_category = 'Blend_My_NFTs'
#
# def draw(self, context):
# layout = self.layout
# scene = context.scene
# mytool = scene.my_tool
#
# row = layout.row()
# row.prop(mytool, "apiKey")
def redraw_panel():
try:
bpy.utils.unregister_class(BMNFTS_PT_MainPanel)
except:
pass
bpy.utils.register_class(BMNFTS_PT_MainPanel)
# Register and Unregister classes from Blender:
classes = (BMNFTS_PGT_MyProperties, BMNFTS_PT_MainPanel, BMNFTS_PT_API_Panel, createData, exportNFTs, refactor_Batches)
classes = (
BMNFTS_PGT_MyProperties,
BMNFTS_PT_MainPanel,
# BMNFTS_PT_LOGIC_Panel,
# BMNFTS_PT_MATERIALS_Panel,
# BMNFTS_PT_API_Panel,
createData,
exportNFTs,
refactor_Batches,
# UIList 1:
UIList.CUSTOM_OT_actions,
UIList.CUSTOM_OT_addViewportSelection,
UIList.CUSTOM_OT_printItems,
UIList.CUSTOM_OT_clearList,
UIList.CUSTOM_OT_removeDuplicates,
UIList.CUSTOM_OT_selectItems,
UIList.CUSTOM_OT_deleteObject,
UIList.CUSTOM_UL_items,
UIList.CUSTOM_PT_objectList,
UIList.CUSTOM_PG_objectCollection,
)
def register():
for cls in classes:
@ -264,12 +354,19 @@ def register():
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_MyProperties)
# Custom scene properties UIList1
bpy.types.Scene.custom = bpy.props.CollectionProperty(type=UIList.CUSTOM_PG_objectCollection)
bpy.types.Scene.custom_index = bpy.props.IntProperty()
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
del bpy.types.Scene.my_tool
del bpy.types.Scene.custom
del bpy.types.Scene.custom_index
if __name__ == '__main__':
register()

Wyświetl plik

@ -147,8 +147,17 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path
os.makedirs(animationFolder)
bpy.context.scene.render.filepath = animationPath
bpy.context.scene.render.image_settings.file_format = animationFileFormat
bpy.ops.render.render(animation=True)
if animationFileFormat == 'MP4':
bpy.context.scene.render.image_settings.file_format = "FFMPEG"
bpy.context.scene.render.ffmpeg.format = 'MPEG4'
bpy.context.scene.render.ffmpeg.codec = 'H264'
bpy.ops.render.render(animation=True)
else:
bpy.context.scene.render.image_settings.file_format = animationFileFormat
bpy.ops.render.render(animation=True)
if enableModelsBlender:
print(f"{bcolors.OK}Generating 3D Model{bcolors.RESET}")
@ -191,8 +200,12 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path
bpy.ops.export_scene.x3d(filepath=f"{modelPath}.x3d",
check_existing=True,
use_selection=True)
elif modelFileFormat == 'STL':
bpy.ops.export_mesh.stl(filepath=f"{modelPath}.stl",
check_existing=True,
use_selection=True)
elif modelFileFormat == 'VOX':
bpy.ops.export_vox.some_data(filepath=f"{modelPath}.x3d")
bpy.ops.export_vox.some_data(filepath=f"{modelPath}.vox")
if not os.path.exists(metaDataFolder):
os.makedirs(metaDataFolder)

0
main/__init__.py 100644
Wyświetl plik

Wyświetl plik

@ -0,0 +1,243 @@
import bpy
import re
import copy
enableGeneration = False
colorList = []
class bcolors:
'''
The colour of console messages.
'''
OK = '\033[92m' # GREEN
WARNING = '\033[93m' # YELLOW
ERROR = '\033[91m' # RED
RESET = '\033[0m' # RESET COLOR
def stripColorFromName(name):
return "_".join(name.split("_")[:-1])
def get_combinations_from_scene():
'''
Generates important variables, dictionaries, and lists needed to be stored to catalog the NFTs.
:return: listAllCollections, attributeCollections, attributeCollections1, hierarchy, variantMetaData, possibleCombinations
'''
coll = bpy.context.scene.collection
try:
scriptIgnore = bpy.data.collections["Script_Ignore"]
except:
print(f"{bcolors.ERROR} ERROR:\nScript_Ignore collection is not in .blend file scene. Please add the Script_Ignore collection to your "
f".blend file scene. For more information, read the README.md file.\n {bcolors.RESET}")
listAllCollInScene = []
listAllCollections = []
def traverse_tree(t):
yield t
for child in t.children:
yield from traverse_tree(child)
for c in traverse_tree(coll):
listAllCollInScene.append(c)
def listSubIgnoreCollections():
def getParentSubCollections(collection):
yield collection
for child in collection.children:
yield from getParentSubCollections(child)
collList = []
for c in getParentSubCollections(scriptIgnore):
collList.append(c.name)
return collList
ignoreList = listSubIgnoreCollections()
for i in listAllCollInScene:
if enableGeneration:
if i.name in colorList:
for j in range(len(colorList[i.name])):
if i.name[-1].isdigit() and i.name not in ignoreList:
listAllCollections.append(i.name + "_" + str(j + 1))
elif j == 0:
listAllCollections.append(i.name)
elif i.name[-1].isdigit() and i.name not in ignoreList:
listAllCollections.append(i.name + "_0")
else:
listAllCollections.append(i.name)
else:
listAllCollections.append(i.name)
listAllCollections.remove(scriptIgnore.name)
if "Scene Collection" in listAllCollections:
listAllCollections.remove("Scene Collection")
if "Master Collection" in listAllCollections:
listAllCollections.remove("Master Collection")
def allScriptIgnore(collection):
'''
Removes all collections, sub collections in Script_Ignore collection from listAllCollections.
'''
for coll in list(collection.children):
listAllCollections.remove(coll.name)
listColl = list(coll.children)
if len(listColl) > 0:
allScriptIgnore(coll)
allScriptIgnore(scriptIgnore)
listAllCollections.sort()
exclude = ["_", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
attributeCollections = copy.deepcopy(listAllCollections)
def filter_num():
"""
This function removes items from 'attributeCollections' if they include values from the 'exclude' variable.
It removes child collections from the parent collections in from the "listAllCollections" list.
"""
for x in attributeCollections:
if any(a in x for a in exclude):
attributeCollections.remove(x)
for i in range(len(listAllCollections)):
filter_num()
attributeVariants = [x for x in listAllCollections if x not in attributeCollections]
attributeCollections1 = copy.deepcopy(attributeCollections)
def attributeData(attributeVariants):
"""
Creates a dictionary of each attribute
"""
allAttDataList = {}
count = 0
previousAttribute = ""
for i in attributeVariants:
def getName(i):
"""
Returns the name of "i" attribute variant
"""
name = i.split("_")[0]
return name
def getOrder_rarity(i):
"""
Returns the "order", "rarity" and "color" (if enabled) of i attribute variant in a list
"""
x = re.sub(r'[a-zA-Z]', "", i)
a = x.split("_")
del a[0]
return list(a)
name = getName(i)
orderRarity = getOrder_rarity(i)
if len(orderRarity) == 0:
print(f"{bcolors.ERROR} \nERROR: {bcolors.RESET}")
print(f"The collection {i} doesn't follow the naming conventions of attributes. Please move this \n"
"colleciton to Script_Ignore or review proper collection format in README.md")
return
elif len(orderRarity) > 0:
number = orderRarity[0]
if enableGeneration:
if count == 1 or count == 0:
previousAttribute = i.partition("_")[0]
count +=1
elif i.partition("_")[0] == previousAttribute:
count +=1
else:
count = 1
number = str(count)
rarity = orderRarity[1]
if enableGeneration and stripColorFromName(i) in colorList:
color = orderRarity[2]
else:
color = "0"
eachObject = {"name": name, "number": number, "rarity": rarity, "color": color}
allAttDataList[i] = eachObject
return allAttDataList
variantMetaData = attributeData(attributeVariants)
def getHierarchy():
"""
Constructs the hierarchy dictionary from attributeCollections1 and variantMetaData.
"""
hierarchy = {}
for i in attributeCollections1:
colParLong = list(bpy.data.collections[str(i)].children)
colParShort = {}
for x in colParLong:
if enableGeneration:
"""
Append colors to blender name for PNG generator and NFTRecord.json to create the correct list
"""
if x.name in colorList:
for j in range(len(colorList[x.name])):
colParShort[x.name + "_" + str(j+1)] = None
else:
colParShort[x.name + "_0"] = None
else:
colParShort[x.name] = None
hierarchy[i] = colParShort
for a in hierarchy:
for b in hierarchy[a]:
for x in variantMetaData:
if str(x) == str(b):
(hierarchy[a])[b] = variantMetaData[x]
return hierarchy
hierarchy = getHierarchy()
def numOfCombinations(hierarchy):
"""
Returns "combinations" the number of all possible NFT combinations.
"""
hierarchyByNum = []
for i in hierarchy:
# Ignore Collections with nothing in them
if len(hierarchy[i]) != 0:
hierarchyByNum.append(len(hierarchy[i]))
else:
print(f"The following collection has been identified as empty: {i}")
combinations = 1
for i in hierarchyByNum:
combinations = combinations*i
if combinations == 0:
print(bcolors.ERROR + "\nERROR:" + bcolors.RESET)
print("The number of all possible combinations is equal to 0. Please review your collection hierarchy"
"and ensure it is formatted correctly. Please review README.md for more information. \nHere is the "
"hierarchy of all collections the DNA_Generator gathered from your .blend file, excluding those in "
f"Script_Ignore: {hierarchy}")
return combinations
possibleCombinations = numOfCombinations(hierarchy)
for i in variantMetaData:
def cameraToggle(i, toggle=True):
if enableGeneration:
"""
Remove Color code so blender recognises the collection
"""
i = stripColorFromName(i)
bpy.data.collections[i].hide_render = toggle
bpy.data.collections[i].hide_viewport = toggle
cameraToggle(i)
return possibleCombinations
if __name__ == '__main__':
get_combinations_from_scene()

359
ui_Lists/UIList.py 100644
Wyświetl plik

@ -0,0 +1,359 @@
import bpy
from bpy.props import (IntProperty,
BoolProperty,
StringProperty,
CollectionProperty,
PointerProperty)
from bpy.types import (Operator,
Panel,
PropertyGroup,
UIList)
# -------------------------------------------------------------------
# Operators
# -------------------------------------------------------------------
class CUSTOM_OT_actions(Operator):
"""Move items up and down, add and remove"""
bl_idname = "custom.list_action"
bl_label = "List Actions"
bl_description = "Move items up and down, add and remove"
bl_options = {'REGISTER'}
action: bpy.props.EnumProperty(
items=(
('UP', "Up", ""),
('DOWN', "Down", ""),
('REMOVE', "Remove", ""),
('ADD', "Add", "")))
def invoke(self, context, event):
scn = context.scene
idx = scn.custom_index
try:
item = scn.custom[idx]
except IndexError:
pass
else:
if self.action == 'DOWN' and idx < len(scn.custom) - 1:
item_next = scn.custom[idx + 1].name
scn.custom.move(idx, idx + 1)
scn.custom_index += 1
info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
self.report({'INFO'}, info)
elif self.action == 'UP' and idx >= 1:
item_prev = scn.custom[idx - 1].name
scn.custom.move(idx, idx - 1)
scn.custom_index -= 1
info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
self.report({'INFO'}, info)
elif self.action == 'REMOVE':
info = 'Item "%s" removed from list' % (scn.custom[idx].name)
scn.custom_index -= 1
scn.custom.remove(idx)
self.report({'INFO'}, info)
if self.action == 'ADD':
if context.object:
item = scn.custom.add()
item.name = context.object.name
item.obj = context.object
scn.custom_index = len(scn.custom) - 1
info = '"%s" added to list' % (item.name)
self.report({'INFO'}, info)
else:
self.report({'INFO'}, "Nothing selected in the Viewport")
return {"FINISHED"}
class CUSTOM_OT_addViewportSelection(Operator):
"""Add all items currently selected in the viewport"""
bl_idname = "custom.add_viewport_selection"
bl_label = "Add Viewport Selection to List"
bl_description = "Add all items currently selected in the viewport"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scn = context.scene
selected_objs = context.selected_objects
if selected_objs:
new_objs = []
for i in selected_objs:
item = scn.custom.add()
item.name = i.name
item.obj = i
new_objs.append(item.name)
info = ', '.join(map(str, new_objs))
self.report({'INFO'}, 'Added: "%s"' % (info))
else:
self.report({'INFO'}, "Nothing selected in the Viewport")
return {'FINISHED'}
class CUSTOM_OT_printItems(Operator):
"""Print all items and their properties to the console"""
bl_idname = "custom.print_items"
bl_label = "Print Items to Console"
bl_description = "Print all items and their properties to the console"
bl_options = {'REGISTER', 'UNDO'}
reverse_order: BoolProperty(
default=False,
name="Reverse Order")
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def execute(self, context):
scn = context.scene
if self.reverse_order:
for i in range(scn.custom_index, -1, -1):
ob = scn.custom[i].obj
print("Object:", ob, "-", ob.name, ob.type)
else:
for item in scn.custom:
ob = item.obj
print("Object:", ob, "-", ob.name, ob.type)
return {'FINISHED'}
class CUSTOM_OT_clearList(Operator):
"""Clear all items of the list"""
bl_idname = "custom.clear_list"
bl_label = "Clear List"
bl_description = "Clear all items of the list"
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
if bool(context.scene.custom):
context.scene.custom.clear()
self.report({'INFO'}, "All items removed")
else:
self.report({'INFO'}, "Nothing to remove")
return {'FINISHED'}
class CUSTOM_OT_removeDuplicates(Operator):
"""Remove all duplicates"""
bl_idname = "custom.remove_duplicates"
bl_label = "Remove Duplicates"
bl_description = "Remove all duplicates"
bl_options = {'INTERNAL'}
def find_duplicates(self, context):
"""find all duplicates by name"""
name_lookup = {}
for c, i in enumerate(context.scene.custom):
name_lookup.setdefault(i.obj.name, []).append(c)
duplicates = set()
for name, indices in name_lookup.items():
for i in indices[1:]:
duplicates.add(i)
return sorted(list(duplicates))
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def execute(self, context):
scn = context.scene
removed_items = []
# Reverse the list before removing the items
for i in self.find_duplicates(context)[::-1]:
scn.custom.remove(i)
removed_items.append(i)
if removed_items:
scn.custom_index = len(scn.custom) - 1
info = ', '.join(map(str, removed_items))
self.report({'INFO'}, "Removed indices: %s" % (info))
else:
self.report({'INFO'}, "No duplicates")
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
class CUSTOM_OT_selectItems(Operator):
"""Select Items in the Viewport"""
bl_idname = "custom.select_items"
bl_label = "Select Item(s) in Viewport"
bl_description = "Select Items in the Viewport"
bl_options = {'REGISTER', 'UNDO'}
select_all = BoolProperty(
default=False,
name="Select all Items of List",
options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def execute(self, context):
scn = context.scene
idx = scn.custom_index
try:
item = scn.custom[idx]
except IndexError:
self.report({'INFO'}, "Nothing selected in the list")
return {'CANCELLED'}
obj_error = False
bpy.ops.object.select_all(action='DESELECT')
if not self.select_all:
name = scn.custom[idx].obj.name
obj = scn.objects.get(name, None)
if not obj:
obj_error = True
else:
obj.select_set(True)
info = '"%s" selected in Vieport' % (obj.name)
else:
selected_items = []
unique_objs = set([i.obj.name for i in scn.custom])
for i in unique_objs:
obj = scn.objects.get(i, None)
if obj:
obj.select_set(True)
selected_items.append(obj.name)
if not selected_items:
obj_error = True
else:
missing_items = unique_objs.difference(selected_items)
if not missing_items:
info = '"%s" selected in Viewport' \
% (', '.join(map(str, selected_items)))
else:
info = 'Missing items: "%s"' \
% (', '.join(map(str, missing_items)))
if obj_error:
info = "Nothing to select, object removed from scene"
self.report({'INFO'}, info)
return {'FINISHED'}
class CUSTOM_OT_deleteObject(Operator):
"""Delete object from scene"""
bl_idname = "custom.delete_object"
bl_label = "Remove Object from Scene"
bl_description = "Remove object from scene"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
scn = context.scene
selected_objs = context.selected_objects
idx = scn.custom_index
try:
item = scn.custom[idx]
except IndexError:
pass
else:
ob = scn.objects.get(item.obj.name)
if not ob:
self.report({'INFO'}, "No object of that name found in scene")
return {"CANCELLED"}
else:
bpy.ops.object.select_all(action='DESELECT')
ob.select_set(True)
bpy.ops.object.delete()
info = ' Item "%s" removed from Scene' % (len(selected_objs))
scn.custom_index -= 1
scn.custom.remove(idx)
self.report({'INFO'}, info)
return {'FINISHED'}
# -------------------------------------------------------------------
# Drawing
# -------------------------------------------------------------------
class CUSTOM_UL_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
obj = item.obj
custom_icon = "OUTLINER_OB_%s" % obj.type
split = layout.split(factor=0.3)
split.label(text="Index: %d" % (index))
split.prop(obj, "name", text="", emboss=False, translate=False, icon=custom_icon)
def invoke(self, context, event):
pass
class CUSTOM_PT_objectList(Panel):
"""Adds a custom panel to the TEXT_EDITOR"""
bl_label = "UI List Test"
bl_idname = "BMNFTS_PT_uilisttest"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Blend_My_NFTs'
def draw(self, context):
layout = self.layout
scn = bpy.context.scene
rows = 2
row = layout.row()
row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows)
col = row.column(align=True)
col.operator("custom.list_action", icon='ADD', text="").action = 'ADD'
col.operator("custom.list_action", icon='REMOVE', text="").action = 'REMOVE'
col.separator()
col.operator("custom.list_action", icon='TRIA_UP', text="").action = 'UP'
col.operator("custom.list_action", icon='TRIA_DOWN', text="").action = 'DOWN'
row = layout.row()
col = row.column(align=True)
row = col.row(align=True)
row.operator("custom.print_items", icon="LINENUMBERS_ON")
row = col.row(align=True)
row.operator("custom.clear_list", icon="X")
row.operator("custom.remove_duplicates", icon="GHOST_ENABLED")
row = layout.row()
col = row.column(align=True)
row = col.row(align=True)
row.operator("custom.add_viewport_selection", icon="HAND") # LINENUMBERS_OFF, ANIM
row = col.row(align=True)
row.operator("custom.select_items", icon="VIEW3D", text="Select Item in 3D View")
row.operator("custom.select_items", icon="GROUP", text="Select All Items in 3D View").select_all = True
row = layout.row()
row = col.row(align=True)
row.operator("custom.delete_object", icon="X")
# -------------------------------------------------------------------
# Collection
# -------------------------------------------------------------------
class CUSTOM_PG_objectCollection(PropertyGroup):
# name: StringProperty() -> Instantiated by default
obj: PointerProperty(
name="Object",
type=bpy.types.Object)

Wyświetl plik