Adding UIList functionality to Custom Metadata Fields

Implemented UIlist method. You are now able to create custom metadata fields all in Blender and each field is dynamically added to the Metadata template that the user selects.
pull/94/head
Torrin Leonard 2022-04-18 11:06:04 -04:00
rodzic 3de95e30d8
commit ee7a4dab41
4 zmienionych plików z 251 dodań i 34 usunięć

Wyświetl plik

@ -13,6 +13,16 @@ bl_info = {
import bpy
from bpy.app.handlers import persistent
from bpy.props import (IntProperty,
BoolProperty,
StringProperty,
EnumProperty,
CollectionProperty)
from bpy.types import (Operator,
Panel,
PropertyGroup,
UIList)
import os
import sys
@ -22,24 +32,40 @@ import importlib
# "a little hacky bs" - Matthew TheBrochacho ;)
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
if bpy in locals():
importlib.reload(DNA_Generator)
importlib.reload(Batch_Sorter)
importlib.reload(Exporter)
importlib.reload(Refactorer)
importlib.reload(get_combinations)
importlib.reload(Checks)
importlib.reload(HeadlessUtil)
else:
from main import \
DNA_Generator, \
Batch_Sorter, \
Exporter, \
Refactorer, \
get_combinations, \
Checks, \
HeadlessUtil
from main import \
Batch_Sorter, \
Checks, \
DNA_Generator, \
Exporter, \
get_combinations, \
HeadlessUtil, \
loading_animation, \
Logic, \
Metadata, \
Rarity, \
Refactorer, \
UILists
if "bpy" in locals():
modules = {
"Batch_Sorter": Batch_Sorter,
"Checks": Checks,
"DNA_Generator": DNA_Generator,
"Exporter": Exporter,
"get_combinations": get_combinations,
"HeadlessUtil": HeadlessUtil,
"loading_animation": loading_animation,
"Logic": Logic,
"Metadata": Metadata,
"Rarity": Rarity,
"Refactorer": Refactorer,
"UILists": UILists
}
for i in modules:
if i in locals():
importlib.reload(modules[i])
# ======== Persistant UI Refresh ======== #
@ -433,17 +459,22 @@ class refactor_Batches(bpy.types.Operator):
bl_description = 'This action cannot be undone.'
bl_options = {'REGISTER', 'INTERNAL'}
reverse_order: BoolProperty(
default=False,
name="Reverse Order")
@classmethod
def poll(cls, context):
return True
return bool(context.scene.custom)
def execute(self, context):
class refactor_panel_input:
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
custom_Fields_File = bpy.path.abspath(bpy.context.scene.input_tool.customfieldsFile)
enableCustomFields = bpy.context.scene.input_tool.enableCustomFields
custom_Fields = {}
cardanoMetaDataBool = bpy.context.scene.input_tool.cardanoMetaDataBool
solanaMetaDataBool = bpy.context.scene.input_tool.solanaMetaDataBool
@ -455,6 +486,26 @@ class refactor_Batches(bpy.types.Operator):
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
# Handling Custom Fields UIList input:
if refactor_panel_input.enableCustomFields:
scn = context.scene
if self.reverse_order:
for i in range(scn.custom_index, -1, -1):
item = scn.custom[i]
if item.field_name in list(refactor_panel_input.custom_Fields.keys()):
raise ValueError(f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
else:
refactor_panel_input.custom_Fields[item.field_name] = item.field_value
else:
for item in scn.custom:
if item.field_name in list(refactor_panel_input.custom_Fields.keys()):
raise ValueError(f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
else:
refactor_panel_input.custom_Fields[item.field_name] = item.field_value
print(refactor_panel_input.custom_Fields)
# Passing info to main functions for refactoring:
Refactorer.reformatNFTCollection(refactor_panel_input)
self.report({'INFO'}, "Batches Refactored, MetaData created!")
@ -671,9 +722,28 @@ class BMNFTS_PT_Refactor(bpy.types.Panel):
row = layout.row()
row.prop(input_tool_scene, "enableCustomFields")
if bpy.context.scene.input_tool.enableCustomFields:
layout = self.layout
scn = bpy.context.scene
rows = 2
row = layout.row()
row.prop(input_tool_scene, "customfieldsFile")
row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows)
col = row.column(align=True)
col.operator("custom.list_action", icon='ZOOM_IN', text="").action = 'ADD'
col.operator("custom.list_action", icon='ZOOM_OUT', 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.label(text=f"*Field Names must be unique.")
row = col.row(align=True)
row.operator("custom.clear_list", icon="X")
row = layout.row()
self.layout.operator("refactor.batches", icon='FOLDER_REDIRECT', text="Refactor Batches & Create Metadata")
@ -714,6 +784,11 @@ class BMNFTS_PT_Other(bpy.types.Panel):
row.operator("wm.url_open", text="YouTube Tutorials",
icon='URL').url = "https://www.youtube.com/watch?v=ygKJYz4BjRs&list=PLuVvzaanutXcYtWmPVKu2bx83EYNxLRsX"
row = layout.row()
row.operator("wm.url_open", text="Join Our Discord Community!",
icon='URL').url = "https://discord.gg/UpZt5Un57t"
# ======== Blender add-on register/unregister handling ======== #
classes = (
@ -732,19 +807,24 @@ classes = (
BMNFTS_PT_GenerateNFTs,
BMNFTS_PT_Refactor,
BMNFTS_PT_Other,
)
) + UILists.classes_UILists
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.input_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_Input_Properties)
bpy.types.Scene.custom = CollectionProperty(type=UILists.CUSTOM_objectCollection)
bpy.types.Scene.custom_index = IntProperty()
def unregister():
for cls in classes:
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.input_tool
del bpy.types.Scene.custom
del bpy.types.Scene.custom_index
if __name__ == '__main__':

Wyświetl plik

@ -6,10 +6,9 @@
# This file returns the specified meta data format to the Exporter.py for a given NFT DNA.
import bpy
import json
# Cardano Template
def returnCardanoMetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enableCustomFields, cardano_description):
def returnCardanoMetaData(name, NFT_DNA, NFT_Variants, custom_Fields, enableCustomFields, cardano_description):
metaDataDictCardano = {"721": {
"<policy_id>": {
name: {
@ -27,14 +26,13 @@ def returnCardanoMetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enabl
# Custom Fields:
if enableCustomFields:
custom_Fields = json.load(open(custom_Fields_File))
for i in custom_Fields:
metaDataDictCardano["721"]["<policy_id>"][name][i] = custom_Fields[i]
return metaDataDictCardano
# Solana Template
def returnSolanaMetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enableCustomFields, solana_description):
def returnSolanaMetaData(name, NFT_DNA, NFT_Variants, custom_Fields, enableCustomFields, solana_description):
metaDataDictSolana = {"name": name, "symbol": "", "description": solana_description, "seller_fee_basis_points": None,
"image": "", "animation_url": "", "external_url": ""}
@ -50,7 +48,6 @@ def returnSolanaMetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enable
# Custom Fields:
if enableCustomFields:
custom_Fields = json.load(open(custom_Fields_File))
for i in custom_Fields:
dictionary = {
"trait_type": i,
@ -72,7 +69,7 @@ def returnSolanaMetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enable
return metaDataDictSolana
# ERC721 Template
def returnErc721MetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enableCustomFields, erc721_description):
def returnErc721MetaData(name, NFT_DNA, NFT_Variants, custom_Fields, enableCustomFields, erc721_description):
metaDataDictErc721 = {
"name": name,
"description": erc721_description,
@ -92,7 +89,6 @@ def returnErc721MetaData(name, NFT_DNA, NFT_Variants, custom_Fields_File, enable
# Custom Fields:
if enableCustomFields:
custom_Fields = json.load(open(custom_Fields_File))
for i in custom_Fields:
dictionary = {
"trait_type": i,

Wyświetl plik

@ -73,9 +73,12 @@ def renameMetaData(rename_MetaData_Variables):
cardanoJsonNew = "Cardano_" + i
cardanoNewName = name.split("_")[0] + "_" + str(file_num)
metaDataDictCardano = Metadata.returnCardanoMetaData(cardanoNewName, NFT_DNA, NFT_Variants, rename_MetaData_Variables.custom_Fields_File, rename_MetaData_Variables.enableCustomFields, rename_MetaData_Variables.cardano_description)
metaDataDictCardano = Metadata.returnCardanoMetaData(cardanoNewName, NFT_DNA, NFT_Variants,
rename_MetaData_Variables.custom_Fields,
rename_MetaData_Variables.enableCustomFields,
rename_MetaData_Variables.cardano_description)
sendMetaDataToJson(metaDataDictCardano, cardanoMetaDataPath, cardanoJsonNew,)
sendMetaDataToJson(metaDataDictCardano, cardanoMetaDataPath, cardanoJsonNew, )
if rename_MetaData_Variables.solanaMetaDataBool:
if not os.path.exists(solanaMetaDataPath):
@ -84,7 +87,10 @@ def renameMetaData(rename_MetaData_Variables):
solanaJsonNew = "Solana_" + i
solanaNewName = name.split("_")[0] + "_" + str(file_num)
metaDataDictSolana = Metadata.returnSolanaMetaData(solanaNewName, NFT_DNA, NFT_Variants, rename_MetaData_Variables.custom_Fields_File, rename_MetaData_Variables.enableCustomFields, rename_MetaData_Variables.solana_description)
metaDataDictSolana = Metadata.returnSolanaMetaData(solanaNewName, NFT_DNA, NFT_Variants,
rename_MetaData_Variables.custom_Fields,
rename_MetaData_Variables.enableCustomFields,
rename_MetaData_Variables.solana_description)
sendMetaDataToJson(metaDataDictSolana, solanaMetaDataPath, solanaJsonNew)
@ -95,7 +101,10 @@ def renameMetaData(rename_MetaData_Variables):
erc721JsonNew = "Erc721_" + i
erc721NewName = name.split("_")[0] + "_" + str(file_num)
metaDataDictErc721 = Metadata.returnErc721MetaData(erc721NewName, NFT_DNA, NFT_Variants, rename_MetaData_Variables.custom_Fields_File, rename_MetaData_Variables.enableCustomFields, rename_MetaData_Variables.erc721_description)
metaDataDictErc721 = Metadata.returnErc721MetaData(erc721NewName, NFT_DNA, NFT_Variants,
rename_MetaData_Variables.custom_Fields,
rename_MetaData_Variables.enableCustomFields,
rename_MetaData_Variables.erc721_description)
sendMetaDataToJson(metaDataDictErc721, erc721MetaDataPath, erc721JsonNew)
return
@ -216,7 +225,7 @@ def reformatNFTCollection(refactor_panel_input):
solanaMetaDataBool = refactor_panel_input.solanaMetaDataBool
erc721MetaData = refactor_panel_input.erc721MetaData
custom_Fields_File = refactor_panel_input.custom_Fields_File
custom_Fields = refactor_panel_input.custom_Fields
enableCustomFields = refactor_panel_input.enableCustomFields

132
main/UILists.py 100644
Wyświetl plik

@ -0,0 +1,132 @@
import bpy
from bpy.props import (IntProperty,
BoolProperty,
StringProperty,
EnumProperty,
CollectionProperty)
from bpy.types import (Operator,
Panel,
PropertyGroup,
UIList)
# ======== Custom Metadata Fields 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 = "Custom Metadata Field" # The name of each 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_clearList(Operator):
"""Clear all items of the list"""
bl_idname = "custom.clear_list"
bl_label = "Clear Custom Fields"
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'}
# UIList class
class CUSTOM_UL_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
split = layout.split(factor=0.1)
split.label(text=f"{index + 1}")
row = split.row()
row.label(text=item.name) # avoids renaming the item by accident
row.prop(item, "field_name", text="")
row.prop(item, "field_value", text="")
def invoke(self, context, event):
pass
# -------------------------------------------------------------------
# Collection
# -------------------------------------------------------------------
class CUSTOM_objectCollection(PropertyGroup):
# name: StringProperty() -> Instantiated by default
obj_type: StringProperty()
obj_id: IntProperty()
field_name: StringProperty(default="Name")
field_value: StringProperty(default="Value")
# -------------------------------------------------------------------
# Register & Unregister
# -------------------------------------------------------------------
classes_UILists = (
CUSTOM_OT_actions,
CUSTOM_OT_clearList,
CUSTOM_UL_items,
CUSTOM_objectCollection,
)