Blend_3D_collections/main/dna_generator.py

337 wiersze
11 KiB
Python

# Purpose:
# This file generates NFT DNA based on a .blend file scene structure and exports NFTRecord.json.
import os
import time
import json
import random
import traceback
from functools import partial
from . import logic, material_generator, helpers
from .helpers import TextColors
def generate_nft_dna(
collection_size,
enable_rarity,
enable_logic,
logic_file,
enable_materials,
materials_file,
enable_debug
):
"""
Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the dna_list.
"""
hierarchy = helpers.get_hierarchy()
# DNA random, Rarity and Logic methods:
data_dictionary = {}
def create_dna_random(hierarchy):
"""Creates a single DNA randomly without Rarity or Logic."""
dna_str = ""
dna_str_list = []
list_option_variant = []
for i in hierarchy:
num_child = len(hierarchy[i])
possible_nums = list(range(1, num_child + 1))
list_option_variant.append(possible_nums)
for i in list_option_variant:
random_variant_num = random.choices(i, k=1)
str1 = ''.join(str(e) for e in random_variant_num)
dna_str_list.append(str1)
for i in dna_str_list:
num = "-" + str(i)
dna_str += num
dna = ''.join(dna_str.split('-', 1))
return str(dna)
def create_dna_rarity(hierarchy):
"""
Sorts through data_dictionary and appropriately weights each variant based on their rarity percentage set in Blender
("rarity" in DNA_Generator). Then
"""
single_dna = ""
for i in hierarchy:
number_list_of_i = []
rarity_list_of_i = []
if_zero_bool = None
for k in hierarchy[i]:
number = hierarchy[i][k]["number"]
number_list_of_i.append(number)
rarity = hierarchy[i][k]["rarity"]
rarity_list_of_i.append(float(rarity))
for x in rarity_list_of_i:
if x == 0:
if_zero_bool = True
elif x != 0:
if_zero_bool = False
try:
if if_zero_bool:
variant_by_num = random.choices(number_list_of_i, k=1)
elif not if_zero_bool:
variant_by_num = random.choices(number_list_of_i, weights=rarity_list_of_i, k=1)
except IndexError:
raise IndexError(
f"\n{TextColors.ERROR}Blend_My_NFTs Error:\n"
f"An issue was found within the Attribute collection '{i}'. For more information on Blend_My_NFTs "
f"compatible scenes, see:\n{TextColors.RESET}"
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
)
single_dna += "-" + str(variant_by_num[0])
single_dna = ''.join(single_dna.split('-', 1))
return single_dna
def singleCompleteDNA():
"""
This function applies Rarity and Logic to a single DNA created by createDNASingle() if Rarity or Logic specified
"""
single_dna = ""
if not enable_rarity:
single_dna = create_dna_random(hierarchy)
# print("============")
# print(f"Original DNA: {single_dna}")
if enable_rarity:
single_dna = create_dna_rarity(hierarchy)
# print(f"Rarity DNA: {single_dna}")
if enable_logic:
single_dna = logic.logicafy_dna_single(hierarchy, single_dna, logic_file, enable_rarity, enable_materials)
# print(f"Logic DNA: {single_dna}")
if enable_materials:
single_dna = material_generator.apply_materials(hierarchy, single_dna, materials_file, enable_rarity)
# print(f"Materials DNA: {single_dna}")
# print("============\n")
return single_dna
def create_dna_list():
"""
Creates dna_list. Loops through createDNARandom() and applies Rarity, and Logic while checking if all DNA
are unique.
"""
dna_set_return = set()
for i in range(collection_size):
dna_push_to_list = partial(singleCompleteDNA)
dna_set_return |= {''.join([dna_push_to_list()]) for _ in range(collection_size - len(dna_set_return))}
dna_list_non_formatted = list(dna_set_return)
dna_list_formatted = []
dna_counter = 1
for i in dna_list_non_formatted:
dna_list_formatted.append({
i: {
"complete": False,
"order_num": dna_counter
}
})
dna_counter += 1
return dna_list_formatted
dna_list = create_dna_list()
# Messages:
helpers.raise_warning_collection_size(dna_list, collection_size)
# Data stored in batchDataDictionary:
data_dictionary["num_nfts_generated"] = len(dna_list)
data_dictionary["hierarchy"] = hierarchy
data_dictionary["dna_list"] = dna_list
return data_dictionary
def make_batches(
collection_size,
nfts_per_batch,
save_path,
batch_json_save_path
):
"""
Sorts through all the batches and outputs a given number of batches depending on collection_size and nfts_per_batch.
These files are then saved as Batch#.json files to batch_json_save_path
"""
# Clears the Batch Data folder of Batches:
batch_list = os.listdir(batch_json_save_path)
if batch_list:
for i in batch_list:
batch = os.path.join(batch_json_save_path, i)
if os.path.exists(batch):
os.remove(
os.path.join(batch_json_save_path, i)
)
blend_my_nf_ts_output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data")
nft_record_save_path = os.path.join(blend_my_nf_ts_output, "NFTRecord.json")
data_dictionary = json.load(open(nft_record_save_path))
num_nfts_generated = data_dictionary["num_nfts_generated"]
hierarchy = data_dictionary["hierarchy"]
dna_list = data_dictionary["dna_list"]
num_batches = collection_size // nfts_per_batch
remainder_dna = collection_size % nfts_per_batch
if remainder_dna > 0:
num_batches += 1
print(f"To generate batches of {nfts_per_batch} DNA sequences per batch, with a total of {num_nfts_generated}"
f" possible NFT DNA sequences, the number of batches generated will be {num_batches}")
batches_dna_list = []
for i in range(num_batches):
if i != range(num_batches)[-1]:
batch_dna_list = list(dna_list[0:nfts_per_batch])
batches_dna_list.append(batch_dna_list)
dna_list = [x for x in dna_list if x not in batch_dna_list]
else:
batch_dna_list = dna_list
batch_dictionary = {
"nfts_in_batch": int(len(batch_dna_list)),
"hierarchy": hierarchy,
"batch_dna_list": batch_dna_list
}
batch_dictionary = json.dumps(batch_dictionary, indent=1, ensure_ascii=True)
with open(os.path.join(batch_json_save_path, f"Batch{i + 1}.json"), "w") as outfile:
outfile.write(batch_dictionary)
def send_to_record(
collection_size,
nfts_per_batch,
save_path,
enable_rarity,
enable_logic,
logic_file,
enable_materials,
materials_file,
blend_my_nfts_output,
batch_json_save_path,
enable_debug
):
"""
Creates NFTRecord.json file and sends "batch_data_dictionary" 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
need to reference this .json file to generate new DNA and make note of the new attributes and variants to prevent
repeat DNA.
"""
# Checking Scene is compatible with BMNFTs:
helpers.check_scene()
# Messages:
print(
f"\n{TextColors.OK}======== Creating NFT Data ========{TextColors.RESET}"
f"\nGenerating {collection_size} NFT DNA"
)
if not enable_rarity and not enable_logic:
print(
f"{TextColors.OK}NFT DNA will be determined randomly, no special properties or parameters are "
f"applied.\n{TextColors.RESET}")
if enable_rarity:
print(
f"{TextColors.OK}Rarity is ON. Weights listed in .blend scene will be taken into account."
f"{TextColors.RESET}"
)
if enable_logic:
print(
f"{TextColors.OK}Logic is ON. {len(list(logic_file.keys()))} rules detected and applied."
f"{TextColors.RESET}"
)
time_start = time.time()
def create_nft_data():
try:
data_dictionary = generate_nft_dna(
collection_size,
enable_rarity,
enable_logic,
logic_file,
enable_materials,
materials_file,
enable_debug,
)
nft_record_save_path = os.path.join(blend_my_nfts_output, "NFTRecord.json")
# Checks:
helpers.raise_warning_max_nfts(nfts_per_batch, collection_size)
helpers.check_duplicates(data_dictionary["dna_list"])
helpers.raise_error_zero_combinations()
if enable_rarity:
helpers.check_rarity(data_dictionary["hierarchy"], data_dictionary["dna_list"],
os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
except FileNotFoundError:
raise FileNotFoundError(
f"\n{TextColors.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, see:\n{TextColors.RESET}"
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
)
finally:
loading.stop()
try:
ledger = json.dumps(data_dictionary, indent=1, ensure_ascii=True)
with open(nft_record_save_path, 'w') as outfile:
outfile.write(ledger + '\n')
print(
f"\n{TextColors.OK}Blend_My_NFTs Success:\n"
f"{len(data_dictionary['DNAList'])} NFT DNA saved to {nft_record_save_path}. NFT DNA Successfully "
f"created.\n{TextColors.RESET}")
except Exception:
traceback.print_exc()
raise (
f"\n{TextColors.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{TextColors.RESET}"
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
)
# Loading Animation:
loading = helpers.Loader(f'Creating NFT DNA...', '').start()
create_nft_data()
make_batches(collection_size, nfts_per_batch, save_path, batch_json_save_path)
loading.stop()
time_end = time.time()
print(
f"{TextColors.OK}Created and saved NFT DNA in {time_end - time_start}s.\n{TextColors.RESET}"
)