kopia lustrzana https://github.com/torrinworx/Blend_My_NFTs
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 testspull/54/head
rodzic
0a06052824
commit
ea84bf23c0
155
__init__.py
155
__init__.py
|
@ -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()
|
||||
|
|
|
@ -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,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()
|
|
@ -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)
|
Ładowanie…
Reference in New Issue