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 handling:
import bpy import bpy
from bpy.app.handlers import persistent
import os import os
import importlib import importlib
from .main import DNA_Generator, Batch_Sorter, Exporter, Batch_Refactorer # Import files from main directory:
importlib.reload(DNA_Generator) importList = ['DNA_Generator', 'Batch_Sorter', 'Exporter', 'Batch_Refactorer', 'get_combinations', 'UIList']
importlib.reload(Batch_Sorter)
importlib.reload(Exporter) if bpy in locals():
importlib.reload(Batch_Refactorer) 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: # User input Property Group:
class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup): class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
# Main BMNFTS Panel properties: # Main BMNFTS Panel properties:
@ -56,9 +72,10 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
name="Animation File Format", name="Animation File Format",
description="Select Animation file format", description="Select Animation file format",
items=[ items=[
('AVI_JPEG', "AVI_JPEG", "Export NFT as AVI_JPEG"), ('AVI_JPEG', '.avi (AVI_JPEG)', 'Export NFT as AVI_JPEG'),
('AVI_RAW', "AVI_RAW", "Export NFT as AVI_RAW"), ('AVI_RAW', '.avi (AVI_RAW)', 'Export NFT as AVI_RAW'),
('FFMPEG', "FFMPEG", "Export NFT as FFMPEG") ('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'), ('FBX', '.fbx', 'Export NFT as .fbx'),
('OBJ', '.obj', 'Export NFT as .obj'), ('OBJ', '.obj', 'Export NFT as .obj'),
('X3D', '.x3d', 'Export NFT as .x3d'), ('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: # API Panel properties:
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD')
def make_directories(save_path): def make_directories(save_path):
Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data") 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") 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) os.makedirs(nftBatch_save_path)
return Blend_My_NFTs_Output, batch_json_save_path, 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): class createData(bpy.types.Operator):
bl_idname = 'create.data' bl_idname = 'create.data'
bl_label = '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, Batch_Refactorer.reformatNFTCollection(save_path, Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path,
cardanoMetaDataBool, solanaMetaDataBool, erc721MetaData) cardanoMetaDataBool, solanaMetaDataBool, erc721MetaData)
# Main Panel: # Main Panel:
class BMNFTS_PT_MainPanel(bpy.types.Panel): class BMNFTS_PT_MainPanel(bpy.types.Panel):
bl_label = "Blend_My_NFTs" bl_label = "Blend_My_NFTs"
@ -186,6 +216,8 @@ class BMNFTS_PT_MainPanel(bpy.types.Panel):
scene = context.scene scene = context.scene
mytool = scene.my_tool mytool = scene.my_tool
layout.label(text=f"Maximum Number Of NFTs: {combinations}")
row = layout.row() row = layout.row()
row.prop(mytool, "nftName") row.prop(mytool, "nftName")
@ -237,26 +269,84 @@ class BMNFTS_PT_MainPanel(bpy.types.Panel):
row.prop(mytool, "erc721MetaData") row.prop(mytool, "erc721MetaData")
self.layout.operator("refactor.batches", icon='MESH_CUBE', text="Refactor Batches & create MetaData") 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 = 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: # 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(): def register():
for cls in classes: for cls in classes:
@ -264,12 +354,19 @@ def register():
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_MyProperties) 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(): def unregister():
for cls in classes: for cls in classes:
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
del bpy.types.Scene.my_tool del bpy.types.Scene.my_tool
del bpy.types.Scene.custom
del bpy.types.Scene.custom_index
if __name__ == '__main__': if __name__ == '__main__':
register() register()

Wyświetl plik

@ -147,8 +147,17 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path
os.makedirs(animationFolder) os.makedirs(animationFolder)
bpy.context.scene.render.filepath = animationPath 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: if enableModelsBlender:
print(f"{bcolors.OK}Generating 3D Model{bcolors.RESET}") 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", bpy.ops.export_scene.x3d(filepath=f"{modelPath}.x3d",
check_existing=True, check_existing=True,
use_selection=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': 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): if not os.path.exists(metaDataFolder):
os.makedirs(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