2021-11-27 01:25:07 +00:00
|
|
|
# Purpose:
|
|
|
|
# This file generates NFT DNA based on a .blend file scene structure and exports NFTRecord.json.
|
|
|
|
|
2021-10-19 19:10:54 +00:00
|
|
|
import bpy
|
|
|
|
import os
|
2021-10-30 15:29:02 +00:00
|
|
|
import re
|
2021-10-19 19:10:54 +00:00
|
|
|
import copy
|
2021-10-30 15:29:02 +00:00
|
|
|
import time
|
2021-10-19 19:10:54 +00:00
|
|
|
import json
|
2021-11-10 04:08:18 +00:00
|
|
|
import random
|
2021-12-02 18:23:07 +00:00
|
|
|
from functools import partial
|
2022-03-25 12:22:28 +00:00
|
|
|
from .loading_animation import Loader
|
2022-03-14 01:27:18 +00:00
|
|
|
from . import Rarity, Logic, Checks
|
2022-03-25 12:22:28 +00:00
|
|
|
|
2022-03-14 01:27:18 +00:00
|
|
|
|
2021-10-30 16:28:38 +00:00
|
|
|
class bcolors:
|
2022-03-25 01:38:53 +00:00
|
|
|
"""
|
2021-11-04 20:48:34 +00:00
|
|
|
The colour of console messages.
|
2022-03-25 01:38:53 +00:00
|
|
|
"""
|
2021-10-30 16:28:38 +00:00
|
|
|
OK = '\033[92m' # GREEN
|
|
|
|
WARNING = '\033[93m' # YELLOW
|
2021-11-01 04:11:48 +00:00
|
|
|
ERROR = '\033[91m' # RED
|
2021-10-30 16:28:38 +00:00
|
|
|
RESET = '\033[0m' # RESET COLOR
|
|
|
|
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
def returnData(maxNFTs, nftsPerBatch):
|
|
|
|
"""
|
2021-10-19 19:10:54 +00:00
|
|
|
Generates important variables, dictionaries, and lists needed to be stored to catalog the NFTs.
|
|
|
|
:return: listAllCollections, attributeCollections, attributeCollections1, hierarchy, variantMetaData, possibleCombinations
|
2022-03-25 01:38:53 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
|
2021-11-06 20:31:42 +00:00
|
|
|
coll = bpy.context.scene.collection
|
2021-12-17 21:48:20 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
scriptIgnore = Checks.raise_Error_ScriptIgnore()
|
2021-12-17 21:48:20 +00:00
|
|
|
|
2021-11-06 20:31:42 +00:00
|
|
|
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)
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2021-11-06 20:31:42 +00:00
|
|
|
for i in listAllCollInScene:
|
2022-03-25 01:38:53 +00:00
|
|
|
listAllCollections.append(i.name)
|
2021-12-17 21:48:20 +00:00
|
|
|
|
2021-10-27 02:25:31 +00:00
|
|
|
listAllCollections.remove(scriptIgnore.name)
|
2021-12-17 21:48:20 +00:00
|
|
|
|
|
|
|
if "Scene Collection" in listAllCollections:
|
|
|
|
listAllCollections.remove("Scene Collection")
|
|
|
|
|
|
|
|
if "Master Collection" in listAllCollections:
|
|
|
|
listAllCollections.remove("Master Collection")
|
2021-10-25 01:40:26 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
def allScriptIgnore(scriptIgnore):
|
|
|
|
"""
|
2021-11-06 20:31:42 +00:00
|
|
|
Removes all collections, sub collections in Script_Ignore collection from listAllCollections.
|
2022-03-25 01:38:53 +00:00
|
|
|
"""
|
|
|
|
for coll in list(scriptIgnore.children):
|
2021-10-27 02:25:31 +00:00
|
|
|
listAllCollections.remove(coll.name)
|
|
|
|
listColl = list(coll.children)
|
|
|
|
if len(listColl) > 0:
|
|
|
|
allScriptIgnore(coll)
|
2021-11-06 20:31:42 +00:00
|
|
|
|
2021-10-27 02:25:31 +00:00
|
|
|
allScriptIgnore(scriptIgnore)
|
2021-11-06 20:31:42 +00:00
|
|
|
listAllCollections.sort()
|
|
|
|
|
2021-12-09 03:41:19 +00:00
|
|
|
exclude = ["_", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
|
2021-10-19 19:10:54 +00:00
|
|
|
attributeCollections = copy.deepcopy(listAllCollections)
|
|
|
|
|
|
|
|
def filter_num():
|
2021-10-30 01:36:34 +00:00
|
|
|
"""
|
|
|
|
This function removes items from 'attributeCollections' if they include values from the 'exclude' variable.
|
2021-10-19 19:10:54 +00:00
|
|
|
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):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
Creates a dictionary of each attribute
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-19 19:10:54 +00:00
|
|
|
allAttDataList = {}
|
2021-11-02 05:28:00 +00:00
|
|
|
count = 0
|
|
|
|
previousAttribute = ""
|
2021-10-19 19:10:54 +00:00
|
|
|
for i in attributeVariants:
|
|
|
|
|
2021-10-30 01:36:34 +00:00
|
|
|
def getName(i):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
Returns the name of "i" attribute variant
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2022-01-16 21:44:52 +00:00
|
|
|
name = i.split("_")[0]
|
2021-10-30 01:36:34 +00:00
|
|
|
return name
|
2021-10-19 19:10:54 +00:00
|
|
|
|
|
|
|
def getOrder_rarity(i):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2022-03-25 01:38:53 +00:00
|
|
|
Returns the "order" and "rarity" (if enabled) of i attribute variant in a list
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-19 19:10:54 +00:00
|
|
|
x = re.sub(r'[a-zA-Z]', "", i)
|
|
|
|
a = x.split("_")
|
|
|
|
del a[0]
|
|
|
|
return list(a)
|
|
|
|
|
2021-10-30 01:36:34 +00:00
|
|
|
name = getName(i)
|
2021-10-19 19:10:54 +00:00
|
|
|
orderRarity = getOrder_rarity(i)
|
2021-10-30 16:28:38 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
number = orderRarity[0]
|
|
|
|
rarity = orderRarity[1]
|
|
|
|
|
|
|
|
eachObject = {"name": name, "number": number, "rarity": rarity}
|
|
|
|
allAttDataList[i] = eachObject
|
2021-10-19 19:10:54 +00:00
|
|
|
return allAttDataList
|
|
|
|
|
|
|
|
variantMetaData = attributeData(attributeVariants)
|
|
|
|
|
2021-10-30 01:36:34 +00:00
|
|
|
def getHierarchy():
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
Constructs the hierarchy dictionary from attributeCollections1 and variantMetaData.
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-22 02:38:00 +00:00
|
|
|
hierarchy = {}
|
|
|
|
for i in attributeCollections1:
|
|
|
|
colParLong = list(bpy.data.collections[str(i)].children)
|
|
|
|
colParShort = {}
|
|
|
|
for x in colParLong:
|
2022-03-25 01:38:53 +00:00
|
|
|
colParShort[x.name] = None
|
2021-10-22 02:38:00 +00:00
|
|
|
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
|
|
|
|
|
2021-10-30 01:36:34 +00:00
|
|
|
hierarchy = getHierarchy()
|
2021-10-22 02:38:00 +00:00
|
|
|
|
|
|
|
def numOfCombinations(hierarchy):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
Returns "combinations" the number of all possible NFT combinations.
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2022-01-16 21:44:52 +00:00
|
|
|
|
2021-10-22 02:38:00 +00:00
|
|
|
hierarchyByNum = []
|
2022-01-16 21:44:52 +00:00
|
|
|
|
2021-10-22 02:38:00 +00:00
|
|
|
for i in hierarchy:
|
2021-11-04 21:43:12 +00:00
|
|
|
# Ignore Collections with nothing in them
|
|
|
|
if len(hierarchy[i]) != 0:
|
|
|
|
hierarchyByNum.append(len(hierarchy[i]))
|
|
|
|
else:
|
2022-01-28 21:56:59 +00:00
|
|
|
print(f"The following collection has been identified as empty: {i}")
|
2022-01-16 21:44:52 +00:00
|
|
|
|
2021-10-22 02:38:00 +00:00
|
|
|
combinations = 1
|
|
|
|
for i in hierarchyByNum:
|
|
|
|
combinations = combinations*i
|
2021-10-30 16:28:38 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
# Checks:
|
|
|
|
numBatches = Checks.raise_Error_numBatches(maxNFTs, nftsPerBatch)
|
2022-01-16 21:44:52 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
Checks.raise_Error_ZeroCombinations(combinations)
|
2022-01-16 21:44:52 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
Checks.raise_Error_numBatchesGreaterThan(numBatches)
|
2021-10-30 16:28:38 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
Checks.raise_Error_numBatchesGreaterThan(numBatches)
|
2021-11-01 04:11:48 +00:00
|
|
|
|
2021-10-22 02:38:00 +00:00
|
|
|
return combinations
|
|
|
|
|
|
|
|
possibleCombinations = numOfCombinations(hierarchy)
|
|
|
|
|
|
|
|
return listAllCollections, attributeCollections, attributeCollections1, hierarchy, possibleCombinations
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
def generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
|
|
|
Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList.
|
|
|
|
"""
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
listAllCollections, attributeCollections, attributeCollections1, hierarchy, possibleCombinations = returnData(maxNFTs, nftsPerBatch)
|
2022-03-12 17:02:01 +00:00
|
|
|
|
|
|
|
# DNA random, Rarity and Logic methods:
|
2021-11-14 04:55:19 +00:00
|
|
|
DataDictionary = {}
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
def createDNArandom():
|
|
|
|
"""Creates a single DNA randomly without Rarity or Logic."""
|
|
|
|
dnaStr = ""
|
|
|
|
dnaStrList = []
|
|
|
|
listOptionVariant = []
|
2021-12-02 18:23:07 +00:00
|
|
|
|
2021-11-10 23:48:49 +00:00
|
|
|
for i in hierarchy:
|
|
|
|
numChild = len(hierarchy[i])
|
|
|
|
possibleNums = list(range(1, numChild + 1))
|
2021-11-11 12:21:27 +00:00
|
|
|
listOptionVariant.append(possibleNums)
|
2021-11-10 23:48:49 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
for i in listOptionVariant:
|
|
|
|
randomVariantNum = random.choices(i, k=1)
|
|
|
|
str1 = ''.join(str(e) for e in randomVariantNum)
|
|
|
|
dnaStrList.append(str1)
|
2021-11-13 23:56:35 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
for i in dnaStrList:
|
|
|
|
num = "-" + str(i)
|
|
|
|
dnaStr += num
|
2021-11-13 23:56:35 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
dna = ''.join(dnaStr.split('-', 1))
|
2021-11-13 23:56:35 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
return str(dna)
|
2021-11-10 23:48:49 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
def singleCompleteDNA():
|
|
|
|
"""This function applies Rarity and Logic to a single DNA created by createDNASingle() if Rarity or Logic specified"""
|
|
|
|
singleDNA = ""
|
|
|
|
if not enableRarity:
|
|
|
|
singleDNA = createDNArandom()
|
|
|
|
# print("============")
|
|
|
|
if enableRarity:
|
2022-03-12 17:18:41 +00:00
|
|
|
singleDNA = Rarity.createDNArarity(hierarchy)
|
2022-03-12 17:02:01 +00:00
|
|
|
# print(f"Rarity DNA: {singleDNA}")
|
2021-11-10 23:48:49 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
if enableLogic:
|
|
|
|
singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile)
|
|
|
|
# print(f"Logic DNA: {singleDNA}")
|
|
|
|
# print("============\n")
|
|
|
|
return singleDNA
|
2021-11-10 04:08:18 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
def create_DNAList():
|
|
|
|
"""Creates DNAList. Loops through createDNARandom() and applies Rarity, and Logic while checking if all DNA are unique"""
|
|
|
|
DNASetReturn = set()
|
2021-11-10 04:08:18 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
for i in range(maxNFTs):
|
|
|
|
dnaPushToList = partial(singleCompleteDNA)
|
2021-11-10 23:48:49 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
DNASetReturn |= {''.join([dnaPushToList()]) for _ in range(maxNFTs - len(DNASetReturn))}
|
2022-03-08 03:21:25 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
DNAListReturn = list(DNASetReturn)
|
2022-03-08 03:21:25 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
return DNAListReturn
|
2022-03-08 03:21:25 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
DNAList = create_DNAList()
|
2022-03-08 03:21:25 +00:00
|
|
|
|
2022-03-12 17:02:01 +00:00
|
|
|
# Messages:
|
2022-02-02 15:37:42 +00:00
|
|
|
if len(DNAList) < maxNFTs:
|
2021-12-20 02:01:08 +00:00
|
|
|
print(f"{bcolors.ERROR} \nWARNING: \n"
|
2022-02-02 15:37:42 +00:00
|
|
|
f"You are seeing this warning because the program cannot generate {maxNFTs} NFTs with rarity enabled. "
|
2021-12-20 02:01:08 +00:00
|
|
|
f"Only {len(DNAList)} NFT DNA were generated."
|
|
|
|
f"Either A) Lower the number of NFTs you wish to create, or B) Increase the maximum number of possible NFTs by"
|
|
|
|
f" creating more variants and attributes in your .blend file.{bcolors.RESET}")
|
|
|
|
|
2021-12-09 03:41:19 +00:00
|
|
|
# Data stored in batchDataDictionary:
|
2021-12-20 02:01:08 +00:00
|
|
|
DataDictionary["numNFTsGenerated"] = len(DNAList)
|
2021-11-14 04:55:19 +00:00
|
|
|
DataDictionary["hierarchy"] = hierarchy
|
|
|
|
DataDictionary["DNAList"] = DNAList
|
2021-10-22 02:38:00 +00:00
|
|
|
|
2022-03-14 01:27:18 +00:00
|
|
|
return DataDictionary, possibleCombinations
|
2021-11-12 04:58:40 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output):
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-30 01:36:34 +00:00
|
|
|
Creates NFTRecord.json file and sends "batchDataDictionary" to it. NFTRecord.json is a permanent record of all DNA
|
|
|
|
you've generated with all attribute variants. If you add new variants or attributes to your .blend file, other scripts
|
2021-10-31 21:09:58 +00:00
|
|
|
need to reference this .json file to generate new DNA and make note of the new attributes and variants to prevent
|
2021-10-30 01:36:34 +00:00
|
|
|
repeate DNA.
|
2022-01-28 21:56:59 +00:00
|
|
|
"""
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
# Messages:
|
|
|
|
print(
|
|
|
|
f"\n========================================\n"
|
|
|
|
f"Creating NFT Data. Generating {maxNFTs} NFT DNA.\n"
|
|
|
|
)
|
|
|
|
|
|
|
|
if not enableRarity and not enableLogic:
|
|
|
|
print(f"{bcolors.OK}NFT DNA will be determined randomly, no special properties or parameters are applied.\n{bcolors.RESET}")
|
|
|
|
|
|
|
|
if enableRarity:
|
|
|
|
print(f"{bcolors.OK}Rarity is ON. Weights listed in .blend scene will be taken into account.\n{bcolors.RESET}")
|
|
|
|
|
|
|
|
if enableLogic:
|
|
|
|
print(f"{bcolors.OK}Logic is ON. Rules listed in {logicFile} will be taken into account.\n{bcolors.RESET}")
|
|
|
|
|
|
|
|
time_start = time.time()
|
|
|
|
def create_nft_data():
|
2022-03-25 12:22:28 +00:00
|
|
|
try:
|
|
|
|
DataDictionary, possibleCombinations = generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic)
|
|
|
|
NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json")
|
2022-02-02 15:37:42 +00:00
|
|
|
|
2022-03-25 12:22:28 +00:00
|
|
|
# Checks:
|
|
|
|
Checks.raise_Warning_maxNFTs(nftsPerBatch, maxNFTs)
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 12:22:28 +00:00
|
|
|
Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"], os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
|
2022-03-14 01:27:18 +00:00
|
|
|
|
2022-03-25 12:22:28 +00:00
|
|
|
Checks.check_Duplicates(DataDictionary["DNAList"])
|
|
|
|
except FileNotFoundError:
|
|
|
|
raise FileNotFoundError(
|
|
|
|
f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n"
|
|
|
|
f"Data not saved to NFTRecord.json. Please review your Blender scene and ensure it follows "
|
|
|
|
f"the naming conventions and scene structure. For more information, "
|
|
|
|
f"see:\n{bcolors.RESET}"
|
|
|
|
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
print("Something unexpected happened. Please check Blender System Console Traceback error for more information.")
|
|
|
|
finally:
|
|
|
|
loading.stop()
|
2021-10-19 19:10:54 +00:00
|
|
|
|
2022-03-25 01:38:53 +00:00
|
|
|
try:
|
|
|
|
ledger = json.dumps(DataDictionary, indent=1, ensure_ascii=True)
|
|
|
|
with open(NFTRecord_save_path, 'w') as outfile:
|
|
|
|
outfile.write(ledger + '\n')
|
|
|
|
|
|
|
|
print(
|
|
|
|
f"\n{bcolors.OK}Blend_My_NFTs Success:\n"
|
|
|
|
f"{len(DataDictionary['DNAList'])} NFT DNA saved to {NFTRecord_save_path}. NFT DNA Successfully created.\n{bcolors.RESET}")
|
|
|
|
|
|
|
|
except:
|
|
|
|
raise (
|
|
|
|
f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n"
|
|
|
|
f"Data not saved to NFTRecord.json. Please review your Blender scene and ensure it follows "
|
|
|
|
f"the naming conventions and scene structure. For more information, "
|
|
|
|
f"see:\n{bcolors.RESET}"
|
|
|
|
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
|
|
|
|
)
|
2022-03-25 12:22:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Loading Animation:
|
|
|
|
loading = Loader(f'Creating NFT DNA...', '').start()
|
|
|
|
create_nft_data()
|
|
|
|
loading.stop()
|
2022-03-25 01:38:53 +00:00
|
|
|
|
|
|
|
time_end = time.time()
|
|
|
|
|
|
|
|
print(
|
2022-03-25 12:22:28 +00:00
|
|
|
f"{bcolors.OK}Created and saved NFT DNA in {time_end - time_start}s.\n{bcolors.RESET}"
|
2022-03-25 01:38:53 +00:00
|
|
|
)
|