kopia lustrzana https://github.com/torrinworx/Blend_My_NFTs
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
rodzic
3de95e30d8
commit
ee7a4dab41
124
__init__.py
124
__init__.py
|
@ -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__':
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
Ładowanie…
Reference in New Issue