From 6495b1265422c1cee3a12f504462068c4d80f3d4 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Fri, 3 Jun 2022 22:47:09 -0400 Subject: [PATCH 01/12] Initial commit --- main/DNA_Generator.py | 4 +- main/Exporter.py | 4 +- main/Logic.py | 183 +++++++++++++++++------------------------- main/Rarity.py | 19 ++++- 4 files changed, 94 insertions(+), 116 deletions(-) diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index a455743..5e702bf 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -199,7 +199,7 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable # print(f"Rarity DNA: {singleDNA}") if enableLogic: - singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile) + singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity) # print(f"Original DNA: {singleDNA}") # print("============\n") @@ -328,7 +328,7 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e 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}") + print(f"{bcolors.OK}Logic is ON. {len(list(logicFile.keys()))} rules from your Logic.json file will be applied.\n{bcolors.RESET}") time_start = time.time() diff --git a/main/Exporter.py b/main/Exporter.py index 4e8eb1e..d1bb513 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -413,13 +413,13 @@ def render_and_save_NFTs(input): if not os.path.exists(solanaMetadataPath): os.makedirs(solanaMetadataPath) createSolanaMetaData(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict, input.custom_Fields, - input.enableCustomFields, input.cardano_description, solanaMetadataPath) + input.enableCustomFields, input.solana_description, solanaMetadataPath) if input.erc721MetaData: if not os.path.exists(erc721MetadataPath): os.makedirs(erc721MetadataPath) createErc721MetaData(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict, input.custom_Fields, - input.enableCustomFields, input.cardano_description, erc721MetadataPath) + input.enableCustomFields, input.erc721_description, erc721MetadataPath) if not os.path.exists(BMNFT_metaData_Folder): os.makedirs(BMNFT_metaData_Folder) diff --git a/main/Logic.py b/main/Logic.py index 2b835aa..157331b 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -2,10 +2,11 @@ # The purpose of this file is to add logic and rules to the DNA that are sent to the NFTRecord.json file in DNA_Generator.py import bpy -import json import random import collections +from .Constants import bcolors, removeList, remove_file_by_extension + # Helper Functions def isAttorVar(hierarchy, items_List): @@ -26,11 +27,13 @@ def isAttorVar(hierarchy, items_List): return dict(items_returned) + def getAttIndex(hierarchy, attribute): attList = list(hierarchy.keys()) index = attList.index(attribute) return index + def getVarNum(variant): if variant == "Empty": num = '0' @@ -38,6 +41,7 @@ def getVarNum(variant): num = variant.split("_")[1] return num + def items_to_num(items_List): num_List = {} for i in items_List: @@ -49,47 +53,70 @@ def items_to_num(items_List): num_List[i] = variant_num_list return num_List -def rar_selectVar(hierarchy, items_List, deconstructed_DNA): - for attribute in items_List: - a_attribute_index = getAttIndex(hierarchy, attribute) +def select_from_then_list(hierarchy, deconstructed_DNA, then_num_list, enableRarity): + for a in then_num_list: - selected_variants = items_List[attribute] - hierarchy_selected_variants = list(hierarchy[attribute]) + a_attribute_index = getAttIndex(hierarchy, a) + selected_variants = then_num_list[a] + hierarchy_selected_variants = list(hierarchy[a]) + + # Left over variants are removed for when the user only specifies individual variants instead of whole attributes left_over_variants = [x for x in hierarchy_selected_variants if x not in selected_variants] + # If 'a' is a full attribute: if not left_over_variants: deconstructed_DNA[int(a_attribute_index)] = "0" + + # If 'a' is only part of an attribute (the user specified variant(s) from the attribute that don't add to the full + # attribute): else: number_List_Of_i = [] rarity_List_Of_i = [] ifZeroBool = None variantNum = None - for a in left_over_variants: - number = a.split("_")[1] - rarity = a.split("_")[2] + for b in left_over_variants: + number = b.split("_")[1] + rarity = b.split("_")[2] number_List_Of_i.append(int(number)) rarity_List_Of_i.append(float(rarity)) - for x in rarity_List_Of_i: - if x == 0: + for b in rarity_List_Of_i: + if b == 0: ifZeroBool = True - elif x != 0: + elif b != 0: ifZeroBool = False - if ifZeroBool: - variantNum = random.choices(number_List_Of_i, k=1) - - if not ifZeroBool: - variantNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) - + if enableRarity: + try: + if ifZeroBool: + variantNum = random.choices(number_List_Of_i, k=1) + elif not ifZeroBool: + variantNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) + else: + try: + variantNum = random.choices(number_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) deconstructed_DNA[int(a_attribute_index)] = str(variantNum[0]) - return deconstructed_DNA + def reconstructDNA(deconstructedDNA): reconstructed_DNA = "" for a in deconstructedDNA: @@ -97,117 +124,57 @@ def reconstructDNA(deconstructedDNA): reconstructed_DNA += num return (''.join(reconstructed_DNA.split('-', 1))) -def strip_empty_variant(num_list): - """Strips empty variants if full attribute collection. Used for processing below.""" - for i in num_list: - var_list = num_list[i] + +def check_if_dna_violates_rules(hierarchy, deconstructed_DNA, if_num_list, then_num_list): + """Returns True if singleDNA violates Rules stated in a Logic.json file.""" + violates_rule = None + + # Strips empty variants if full attribute collection + for i in if_num_list: + var_list = if_num_list[i] if "0" in var_list: var_list.remove("0") - num_list[i] = var_list - return num_list + if_num_list[i] = var_list -# Rule Checks: -def never_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): - """Returns True if singleDNA violates Never with Rule stated in Logic.json.""" - violates_rule = None + for a in if_num_list: + for b in then_num_list: + # The attributes slot value from the DNA given 'a' from if_num_list and 'b' from then_num_list: + attribute_slot_value_IF = str(deconstructed_DNA[getAttIndex(hierarchy, a)]) + attribute_slot_value_THEN = str(deconstructed_DNA[getAttIndex(hierarchy, b)]) - num_List1 = strip_empty_variant(num_List1) - num_List2 = strip_empty_variant(num_List2) - - for a in num_List1: - for b in num_List2: - if str(deconstructed_DNA[getAttIndex(hierarchy, a)]) in num_List1[a] and \ - str(deconstructed_DNA[getAttIndex(hierarchy, b)]) in num_List2[b]: + if attribute_slot_value_IF in if_num_list[a] and attribute_slot_value_THEN in then_num_list[b]: violates_rule = True return violates_rule else: violates_rule = False return violates_rule -def only_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): - """Returns True if singleDNA violates Only with Rule stated in Logic.json.""" - violates_rule = None - - for a in num_List1: - for b in num_List2: - if str(deconstructed_DNA[getAttIndex(hierarchy, a)]) in num_List1[a] and \ - str(deconstructed_DNA[getAttIndex(hierarchy, b)]) not in num_List2[b]: - violates_rule = True - return violates_rule - - else: - violates_rule = False - return violates_rule - -def always_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): - """Returns True if singleDNA violates Always with Rule stated in Logic.json.""" - violates_rule = None - - for a in num_List2: - if str(deconstructed_DNA[getAttIndex(hierarchy, a)]) not in num_List2[a]: - violates_rule = True - return violates_rule - else: - violates_rule = False - return violates_rule - # Main Function -def logicafyDNAsingle(hierarchy, singleDNA, logicFile): +def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity): deconstructed_DNA = singleDNA.split("-") - didReconstruct = True originalDNA = str(singleDNA) while didReconstruct: didReconstruct = False for rule in logicFile: - items_List1 = isAttorVar(hierarchy, logicFile[rule]["Items-1"]) - items_List2 = isAttorVar(hierarchy, logicFile[rule]["Items-2"]) - num_List1 = items_to_num(items_List1) - num_List2 = items_to_num(items_List2) + # Items from 'IF' key for a given rule + if_list = isAttorVar(hierarchy, logicFile[rule]["IF"]) + if_num_list = items_to_num(if_list) - if logicFile[rule]["Rule-Type"] == "Never With": - if never_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): + # Items from 'THEN' key for a given rule + then_list = isAttorVar(hierarchy, logicFile[rule]["THEN"]) + then_num_list = items_to_num(then_list) - rand_bool = bool(random.getrandbits(1)) + if check_if_dna_violates_rules(hierarchy, deconstructed_DNA, if_num_list, then_num_list): + deconstructed_DNA = select_from_then_list(hierarchy, deconstructed_DNA, then_num_list, enableRarity) - if rand_bool: - deconstructed_DNA = rar_selectVar(hierarchy, items_List2, deconstructed_DNA) - - if not rand_bool: - deconstructed_DNA = rar_selectVar(hierarchy, items_List1, deconstructed_DNA) - - newDNA = reconstructDNA(deconstructed_DNA) - if newDNA != originalDNA: - originalDNA = str(newDNA) - didReconstruct = True - break - - if logicFile[rule]["Rule-Type"] == "Only With": - if only_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): - for b in num_List1: - if "0" in num_List1[b]: # If complete attribute - deconstructed_DNA[getAttIndex(hierarchy, b)] = "0" - - if "0" not in num_List1[b]: # Not complete attribute, select from other variants with rarity: - deconstructed_DNA = rar_selectVar(hierarchy, items_List1, deconstructed_DNA) - - newDNA = reconstructDNA(deconstructed_DNA) - if newDNA != originalDNA: - originalDNA = str(newDNA) - didReconstruct = True - break - - if logicFile[rule]["Rule-Type"] == "Always With": - if always_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): - deconstructed_DNA = rar_selectVar(hierarchy, items_List1, deconstructed_DNA) - - newDNA = reconstructDNA(deconstructed_DNA) - if newDNA != originalDNA: - originalDNA = str(newDNA) - didReconstruct = True - break + newDNA = reconstructDNA(deconstructed_DNA) + if newDNA != originalDNA: + originalDNA = str(newDNA) + didReconstruct = True + break return str(reconstructDNA(deconstructed_DNA)) diff --git a/main/Rarity.py b/main/Rarity.py index 2cabc49..606d630 100644 --- a/main/Rarity.py +++ b/main/Rarity.py @@ -4,6 +4,9 @@ import bpy import random +from .Constants import bcolors, removeList, remove_file_by_extension + + def createDNArarity(hierarchy): """ Sorts through DataDictionary and appropriately weights each variant based on their rarity percentage set in Blender @@ -32,10 +35,18 @@ def createDNArarity(hierarchy): elif x != 0: ifZeroBool = False - if ifZeroBool: - variantByNum = random.choices(number_List_Of_i, k=1) - elif not ifZeroBool: - variantByNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) + try: + if ifZeroBool: + variantByNum = random.choices(number_List_Of_i, k=1) + elif not ifZeroBool: + variantByNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Attribute collection '{i}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) singleDNA += "-" + str(variantByNum[0]) singleDNA = ''.join(singleDNA.split('-', 1)) From 17cbf3a32f8549bf04cddff39a6aca3d95922af5 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sun, 5 Jun 2022 23:46:49 -0400 Subject: [PATCH 02/12] Revamping Logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lots more info in the comments of the code. Documentation still needs to be made, also more testing lol This is the new format for Logic.json files: ``` { "Rule-1":{ "IF": [ "Variant collection name" ], "THEN":[ "Attribute collection name", "Variant collection name" ] } } ``` Some basic guidelines, needs to be more thorough and cleaned up for documentation: Attributes and Variants can be used in the “THEN” list, Variants can only be used in the “IF” list. If a full attribute is detected in the “THEN” list (either by the attribute collection name or all variant collection names in that attribute) and a variant is not detected in the “IF” list, the attribute detected in the “THEN” list will be set to “EMPTY”. Variants in the “IF” list may be from the same or separate attributes, however they must never be from the same attribute as variants in the “THEN” list. I need sleep. Note to self: The Logic UI List needs to be updated to reflect these changes to Logics core functionality, the current UI List is broken on this branch and wont work. Also added save_result to Constants.py for basic logging functionality. --- main/Constants.py | 18 +- main/DNA_Generator.py | 550 +++++++++++++++++++++--------------------- main/Logic.py | 359 ++++++++++++++++----------- main/Rarity.py | 3 - 4 files changed, 516 insertions(+), 414 deletions(-) diff --git a/main/Constants.py b/main/Constants.py index a3dd05a..c66676e 100644 --- a/main/Constants.py +++ b/main/Constants.py @@ -2,7 +2,8 @@ # This file is for storing or updating constant values that may need to be changes depending on system requirements and # different usecases. import os - +import json +import platform removeList = [".gitignore", ".DS_Store", "desktop.ini", ".ini"] @@ -34,3 +35,18 @@ class bcolors: ERROR = '\033[91m' # RED RESET = '\033[0m' # RESET COLOR + +def save_result(result): + """ + Saves json result to json file at the specified path. + """ + file_name = "log.json" + if platform.system() == "Linux" or platform.system() == "Darwin": + path = os.path.join(os.path.join(os.path.expanduser('~')), 'Desktop', file_name) + + if platform.system() == "Windows": + path = os.path.join(os.environ["HOMEPATH"], "Desktop", file_name) + + data = json.dumps(result, indent=1, ensure_ascii=True) + with open(path, 'w') as outfile: + outfile.write(data + '\n') diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 5e702bf..bcb6f70 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -15,375 +15,383 @@ from .Constants import bcolors, removeList, remove_file_by_extension def get_hierarchy(): - """ + """ Returns the hierarchy of a given Blender scene. """ - coll = bpy.context.scene.collection + coll = bpy.context.scene.collection - scriptIgnoreCollection = bpy.data.collections["Script_Ignore"] + scriptIgnoreCollection = bpy.data.collections["Script_Ignore"] - listAllCollInScene = [] - listAllCollections = [] + listAllCollInScene = [] + listAllCollections = [] - def traverse_tree(t): - yield t - for child in t.children: - yield from traverse_tree(child) + 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) + for c in traverse_tree(coll): + listAllCollInScene.append(c) - for i in listAllCollInScene: - listAllCollections.append(i.name) + for i in listAllCollInScene: + listAllCollections.append(i.name) - listAllCollections.remove(scriptIgnoreCollection.name) + listAllCollections.remove(scriptIgnoreCollection.name) - if "Scene Collection" in listAllCollections: - listAllCollections.remove("Scene Collection") + if "Scene Collection" in listAllCollections: + listAllCollections.remove("Scene Collection") - if "Master Collection" in listAllCollections: - listAllCollections.remove("Master Collection") + if "Master Collection" in listAllCollections: + listAllCollections.remove("Master Collection") - def allScriptIgnore(scriptIgnoreCollection): - # Removes all collections, sub collections in Script_Ignore collection from listAllCollections. + def allScriptIgnore(scriptIgnoreCollection): + # Removes all collections, sub collections in Script_Ignore collection from listAllCollections. - for coll in list(scriptIgnoreCollection.children): - listAllCollections.remove(coll.name) - listColl = list(coll.children) - if len(listColl) > 0: - allScriptIgnore(coll) + for coll in list(scriptIgnoreCollection.children): + listAllCollections.remove(coll.name) + listColl = list(coll.children) + if len(listColl) > 0: + allScriptIgnore(coll) - allScriptIgnore(scriptIgnoreCollection) - listAllCollections.sort() + allScriptIgnore(scriptIgnoreCollection) + listAllCollections.sort() - exclude = ["_"] # Excluding characters that identify a Variant - attributeCollections = copy.deepcopy(listAllCollections) + exclude = ["_"] # Excluding characters that identify a Variant + 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) + 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() + for i in range(len(listAllCollections)): + filter_num() - attributeVariants = [x for x in listAllCollections if x not in attributeCollections] - attributeCollections1 = copy.deepcopy(attributeCollections) + attributeVariants = [x for x in listAllCollections if x not in attributeCollections] + attributeCollections1 = copy.deepcopy(attributeCollections) - def attributeData(attributeVariants): - """ + def attributeData(attributeVariants): + """ Creates a dictionary of each attribute """ - allAttDataList = {} - for i in attributeVariants: - # Check if name follows naming conventions: - if i.count("_") > 2: - raise Exception( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"There is a naming issue with the following Attribute/Variant: '{i}'\n" - f"Review the naming convention of Attribute and Variant collections here:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) + allAttDataList = {} + for i in attributeVariants: + # Check if name follows naming conventions: + if int(i.count("_")) > 2 and int(i.split("_")[1]) > 0: + raise Exception( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"There is a naming issue with the following Attribute/Variant: '{i}'\n" + f"Review the naming convention of Attribute and Variant collections here:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) - def getName(i): - """ - Returns the name of "i" attribute variant - """ + try: + number = i.split("_")[1] + name = i.split("_")[0] + rarity = i.split("_")[2] + except IndexError: + raise Exception( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"There is a naming issue with the following Attribute/Variant: '{i}'\n" + f"Review the naming convention of Attribute and Variant collections here:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) - name = i.split("_")[0] + allAttDataList[i] = {"name": name, "number": number, "rarity": rarity} + return allAttDataList - return name + variantMetaData = attributeData(attributeVariants) - def getOrder_rarity(i): - """ - Returns the "order" and "rarity" (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) + hierarchy = {} + for i in attributeCollections1: + colParLong = list(bpy.data.collections[str(i)].children) + colParShort = {} + for x in colParLong: + colParShort[x.name] = None + hierarchy[i] = colParShort - name = getName(i) - orderRarity = getOrder_rarity(i) + for a in hierarchy: + for b in hierarchy[a]: + for x in variantMetaData: + if str(x) == str(b): + (hierarchy[a])[b] = variantMetaData[x] - try: - number = orderRarity[0] - except: - raise Exception( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"There is a naming issue with the following Attribute/Variant: '{i}'\n" - f"Review the naming convention of Attribute and Variant collections here:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) + return hierarchy - try: - rarity = orderRarity[1] - except: - raise Exception( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"There is a naming issue with the following Attribute/Variant: '{i}'\n" - f"Review the naming convention of Attribute and Variant collections here:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) +def strip_empty_exclude(hierarchy): + """ + Strips Empty Exclude variants from the hierarchy. + """ + excluded_var_dict = {} - eachObject = {"name": name, "number": number, "rarity": rarity} - allAttDataList[i] = eachObject - return allAttDataList + for a in hierarchy: + empty_variant = "" + empty_var_count = 0 + variant_list = list(hierarchy[a].keys()) + # empty_var_count and raise() prevents this for from causing breaking stuff: - variantMetaData = attributeData(attributeVariants) + for b in variant_list: + if b.split("_")[1] == "0": + empty_variant = b + empty_var_count += 1 + if empty_var_count > 1: + raise Exception( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"The Attribute collection '{a}' has more than one Empty variant.\n" + f"Attributes can only have a maximum of 1 Empty variant, please review the documentation here:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) - hierarchy = {} - for i in attributeCollections1: - colParLong = list(bpy.data.collections[str(i)].children) - colParShort = {} - for x in colParLong: - colParShort[x.name] = None - hierarchy[i] = colParShort + if len(empty_variant.split("_")) == 4 and empty_variant.split("_")[3] == "Exclude": + excluded_var_dict[a] = empty_variant + del hierarchy[a][empty_variant] - 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 + return hierarchy, excluded_var_dict def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile): - """ + """ Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList. """ - hierarchy = get_hierarchy() + hierarchy, excluded_var_dict = strip_empty_exclude(get_hierarchy()) - # DNA random, Rarity and Logic methods: - DataDictionary = {} + # DNA random, Rarity and Logic methods: + DataDictionary = {} - def createDNArandom(): - """Creates a single DNA randomly without Rarity or Logic.""" - dnaStr = "" - dnaStrList = [] - listOptionVariant = [] + def createDNArandom(): + """Creates a single DNA randomly without Rarity or Logic.""" + dnaStr = "" + dnaStrList = [] + listOptionVariant = [] - for i in hierarchy: - numChild = len(hierarchy[i]) - possibleNums = list(range(1, numChild + 1)) - listOptionVariant.append(possibleNums) + for i in hierarchy: + numChild = len(hierarchy[i]) + possibleNums = list(range(1, numChild + 1)) + listOptionVariant.append(possibleNums) - for i in listOptionVariant: - randomVariantNum = random.choices(i, k=1) - str1 = ''.join(str(e) for e in randomVariantNum) - dnaStrList.append(str1) + for i in listOptionVariant: + randomVariantNum = random.choices(i, k=1) + str1 = ''.join(str(e) for e in randomVariantNum) + dnaStrList.append(str1) - for i in dnaStrList: - num = "-" + str(i) - dnaStr += num + for i in dnaStrList: + num = "-" + str(i) + dnaStr += num - dna = ''.join(dnaStr.split('-', 1)) + dna = ''.join(dnaStr.split('-', 1)) - return str(dna) + return str(dna) - def singleCompleteDNA(): - """This function applies Rarity and Logic to a single DNA created by createDNASingle() if Rarity or Logic specified""" - singleDNA = "" - # Comments for debugging random, rarity, logic, and materials. - if not enableRarity: - singleDNA = createDNArandom() - # print("============") - if enableRarity: - singleDNA = Rarity.createDNArarity(hierarchy) - # print(f"Rarity DNA: {singleDNA}") + def singleCompleteDNA(): + """ + This function applies Rarity and Logic to a single DNA created by createDNASingle() if Rarity or Logic specified + """ - if enableLogic: - singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity) - # print(f"Original DNA: {singleDNA}") - # print("============\n") + singleDNA = "" + # Comments for debugging random, rarity, logic, and materials. + if not enableRarity: + singleDNA = createDNArandom() + # print("============") + # print(f"Original DNA: {singleDNA}") + if enableRarity: + singleDNA = Rarity.createDNArarity(hierarchy) + # print(f"Rarity DNA: {singleDNA}") - if enableMaterials: - singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile) - # print(f"Materials DNA: {singleDNA}") - # print("============\n") + if enableLogic: + singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_var_dict) + # print(f"Logic DNA: {singleDNA}") - return singleDNA + if enableMaterials: + singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile) + # print(f"Materials DNA: {singleDNA}") + # print("============\n") - def create_DNAList(): - """Creates DNAList. Loops through createDNARandom() and applies Rarity, and Logic while checking if all DNA are unique""" - DNASetReturn = set() + return singleDNA - for i in range(collectionSize): - dnaPushToList = partial(singleCompleteDNA) + def create_DNAList(): + """Creates DNAList. Loops through createDNARandom() and applies Rarity, and Logic while checking if all DNA are unique""" + DNASetReturn = set() - DNASetReturn |= {''.join([dnaPushToList()]) for _ in range(collectionSize - len(DNASetReturn))} + for i in range(collectionSize): + dnaPushToList = partial(singleCompleteDNA) - DNAListUnformatted = list(DNASetReturn) + DNASetReturn |= {''.join([dnaPushToList()]) for _ in range(collectionSize - len(DNASetReturn))} - DNAListFormatted = [] - DNA_Counter = 1 - for i in DNAListUnformatted: - DNAListFormatted.append({ - i: { - "Complete": False, - "Order_Num": DNA_Counter - } - }) + DNAListUnformatted = list(DNASetReturn) - DNA_Counter += 1 + DNAListFormatted = [] + DNA_Counter = 1 + for i in DNAListUnformatted: + DNAListFormatted.append({ + i: { + "Complete": False, + "Order_Num": DNA_Counter + } + }) - return DNAListFormatted + DNA_Counter += 1 - DNAList = create_DNAList() + return DNAListFormatted - # Messages: + DNAList = create_DNAList() - Checks.raise_Warning_collectionSize(DNAList, collectionSize) + # Messages: - # Data stored in batchDataDictionary: - DataDictionary["numNFTsGenerated"] = len(DNAList) - DataDictionary["hierarchy"] = hierarchy - DataDictionary["DNAList"] = DNAList + Checks.raise_Warning_collectionSize(DNAList, collectionSize) + + # Data stored in batchDataDictionary: + DataDictionary["numNFTsGenerated"] = len(DNAList) + DataDictionary["excludedVariants"] = excluded_var_dict + DataDictionary["hierarchy"] = hierarchy + DataDictionary["DNAList"] = DNAList + + return DataDictionary - return DataDictionary def makeBatches(collectionSize, nftsPerBatch, save_path, batch_json_save_path): - """ + """ Sorts through all the batches and outputs a given number of batches depending on collectionSize and nftsPerBatch. These files are then saved as Batch#.json files to batch_json_save_path """ - # Clears the Batch Data folder of Batches: - batchList = os.listdir(batch_json_save_path) - if batchList: - for i in batchList: - batch = os.path.join(batch_json_save_path, i) - if os.path.exists(batch): - os.remove( - os.path.join(batch_json_save_path, i) - ) + # Clears the Batch Data folder of Batches: + batchList = os.listdir(batch_json_save_path) + if batchList: + for i in batchList: + 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_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data") - NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") - DataDictionary = json.load(open(NFTRecord_save_path)) + Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data") + NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") + DataDictionary = json.load(open(NFTRecord_save_path)) - numNFTsGenerated = DataDictionary["numNFTsGenerated"] - hierarchy = DataDictionary["hierarchy"] - DNAList = DataDictionary["DNAList"] + numNFTsGenerated = DataDictionary["numNFTsGenerated"] + hierarchy = DataDictionary["hierarchy"] + DNAList = DataDictionary["DNAList"] - numBatches = collectionSize // nftsPerBatch - remainder_dna = collectionSize % nftsPerBatch - if remainder_dna > 0: - numBatches += 1 + numBatches = collectionSize // nftsPerBatch + remainder_dna = collectionSize % nftsPerBatch + if remainder_dna > 0: + numBatches += 1 - print(f"To generate batches of {nftsPerBatch} DNA sequences per batch, with a total of {numNFTsGenerated}" - f" possible NFT DNA sequences, the number of batches generated will be {numBatches}") + print(f"To generate batches of {nftsPerBatch} DNA sequences per batch, with a total of {numNFTsGenerated}" + f" possible NFT DNA sequences, the number of batches generated will be {numBatches}") - batches_dna_list = [] + batches_dna_list = [] - for i in range(numBatches): - BatchDNAList = [] - if i != range(numBatches)[-1]: - BatchDNAList = list(DNAList[0:nftsPerBatch]) - batches_dna_list.append(BatchDNAList) + for i in range(numBatches): + BatchDNAList = [] + if i != range(numBatches)[-1]: + BatchDNAList = list(DNAList[0:nftsPerBatch]) + batches_dna_list.append(BatchDNAList) - DNAList = [x for x in DNAList if x not in BatchDNAList] - else: - BatchDNAList = DNAList + DNAList = [x for x in DNAList if x not in BatchDNAList] + else: + BatchDNAList = DNAList - batchDictionary = { - "NFTs_in_Batch": int(len(BatchDNAList)), - "hierarchy": hierarchy, - "BatchDNAList": BatchDNAList - } + batchDictionary = { + "NFTs_in_Batch": int(len(BatchDNAList)), + "hierarchy": hierarchy, + "BatchDNAList": BatchDNAList + } - batchDictionary = json.dumps(batchDictionary, indent=1, ensure_ascii=True) + batchDictionary = json.dumps(batchDictionary, indent=1, ensure_ascii=True) + + with open(os.path.join(batch_json_save_path, f"Batch{i + 1}.json"), "w") as outfile: + outfile.write(batchDictionary) - with open(os.path.join(batch_json_save_path, f"Batch{i + 1}.json"), "w") as outfile: - outfile.write(batchDictionary) def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile, Blend_My_NFTs_Output, batch_json_save_path): - """ + """ 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 need to reference this .json file to generate new DNA and make note of the new attributes and variants to prevent repeate DNA. """ - # Checking Scene is compatible with BMNFTs: - Checks.check_Scene() + # Checking Scene is compatible with BMNFTs: + Checks.check_Scene() - # Messages: - print( - f"\n========================================\n" - f"Creating NFT Data. Generating {collectionSize} NFT DNA.\n" - ) + # Messages: + print( + f"\n========================================\n" + f"Creating NFT Data. Generating {collectionSize} 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 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 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. {len(list(logicFile.keys()))} rules from your Logic.json file will be applied.\n{bcolors.RESET}") + if enableLogic: + print(f"{bcolors.OK}Logic is ON. {len(list(logicFile.keys()))} rules detected and applied.\n{bcolors.RESET}") - time_start = time.time() + time_start = time.time() - def create_nft_data(): - try: - DataDictionary = generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, - materialsFile) - NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") + def create_nft_data(): + try: + DataDictionary = generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, + materialsFile) + NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") - # Checks: + # Checks: - Checks.raise_Warning_maxNFTs(nftsPerBatch, collectionSize) - Checks.check_Duplicates(DataDictionary["DNAList"]) - Checks.raise_Error_ZeroCombinations() + Checks.raise_Warning_maxNFTs(nftsPerBatch, collectionSize) + Checks.check_Duplicates(DataDictionary["DNAList"]) + Checks.raise_Error_ZeroCombinations() - if enableRarity: - Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"], os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data")) + if enableRarity: + Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"], + os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data")) - 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" - ) - finally: - loading.stop() + 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" + ) + finally: + loading.stop() - try: - ledger = json.dumps(DataDictionary, indent=1, ensure_ascii=True) - with open(NFTRecord_save_path, 'w') as outfile: - outfile.write(ledger + '\n') + 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}") + 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" - ) + 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" + ) - # Loading Animation: - loading = Loader(f'Creating NFT DNA...', '').start() - create_nft_data() - makeBatches(collectionSize, nftsPerBatch, save_path, batch_json_save_path) - loading.stop() + # Loading Animation: + loading = Loader(f'Creating NFT DNA...', '').start() + create_nft_data() + makeBatches(collectionSize, nftsPerBatch, save_path, batch_json_save_path) + loading.stop() - time_end = time.time() + time_end = time.time() - print( - f"{bcolors.OK}Created and saved NFT DNA in {time_end - time_start}s.\n{bcolors.RESET}" - ) + print( + f"{bcolors.OK}Created and saved NFT DNA in {time_end - time_start}s.\n{bcolors.RESET}" + ) diff --git a/main/Logic.py b/main/Logic.py index 157331b..230c80d 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -5,153 +5,223 @@ import bpy import random import collections -from .Constants import bcolors, removeList, remove_file_by_extension - - -# Helper Functions -def isAttorVar(hierarchy, items_List): - items_returned = collections.defaultdict(list) - for i in items_List: - for j in hierarchy: - if i == j: # If i is an Attribute, add all i Variants to dictionary. - items_returned[i] = list(hierarchy[j].keys()) - items_returned[i].append("Empty") - - if i in list(hierarchy[j].keys()): - items_returned[j].append(i) - - # Check if all variants in an attribute were included, if so, add "Empty" variant. - for i in items_returned: - if list(items_returned[i]) == list(hierarchy[i].keys()): - items_returned[i].append("Empty") - - return dict(items_returned) - - -def getAttIndex(hierarchy, attribute): - attList = list(hierarchy.keys()) - index = attList.index(attribute) - return index - - -def getVarNum(variant): - if variant == "Empty": - num = '0' - else: - num = variant.split("_")[1] - return num - - -def items_to_num(items_List): - num_List = {} - for i in items_List: - variant_num_list = [] - - for j in items_List[i]: - variant_num_list.append(getVarNum(j)) - - num_List[i] = variant_num_list - return num_List - - -def select_from_then_list(hierarchy, deconstructed_DNA, then_num_list, enableRarity): - for a in then_num_list: - - a_attribute_index = getAttIndex(hierarchy, a) - - selected_variants = then_num_list[a] - hierarchy_selected_variants = list(hierarchy[a]) - - # Left over variants are removed for when the user only specifies individual variants instead of whole attributes - left_over_variants = [x for x in hierarchy_selected_variants if x not in selected_variants] - - # If 'a' is a full attribute: - if not left_over_variants: - deconstructed_DNA[int(a_attribute_index)] = "0" - - # If 'a' is only part of an attribute (the user specified variant(s) from the attribute that don't add to the full - # attribute): - else: - number_List_Of_i = [] - rarity_List_Of_i = [] - ifZeroBool = None - variantNum = None - - for b in left_over_variants: - number = b.split("_")[1] - rarity = b.split("_")[2] - - number_List_Of_i.append(int(number)) - rarity_List_Of_i.append(float(rarity)) - - for b in rarity_List_Of_i: - if b == 0: - ifZeroBool = True - elif b != 0: - ifZeroBool = False - - if enableRarity: - try: - if ifZeroBool: - variantNum = random.choices(number_List_Of_i, k=1) - elif not ifZeroBool: - variantNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) - except IndexError: - raise IndexError( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " - f"see:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) - else: - try: - variantNum = random.choices(number_List_Of_i, k=1) - except IndexError: - raise IndexError( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " - f"see:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) - deconstructed_DNA[int(a_attribute_index)] = str(variantNum[0]) - return deconstructed_DNA - +from .Constants import bcolors, removeList, remove_file_by_extension, save_result def reconstructDNA(deconstructedDNA): reconstructed_DNA = "" for a in deconstructedDNA: num = "-" + str(a) reconstructed_DNA += num - return (''.join(reconstructed_DNA.split('-', 1))) + return ''.join(reconstructed_DNA.split('-', 1)) -def check_if_dna_violates_rules(hierarchy, deconstructed_DNA, if_num_list, then_num_list): - """Returns True if singleDNA violates Rules stated in a Logic.json file.""" - violates_rule = None +def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity, excluded_var_dict): - # Strips empty variants if full attribute collection - for i in if_num_list: - var_list = if_num_list[i] - if "0" in var_list: - var_list.remove("0") - if_num_list[i] = var_list + # Check if Variants in if_dict are in deconstructed_DNA, if so return if_list_selected = True: + if_list_selected = False + for a in deconstructed_DNA: + attribute_index = deconstructed_DNA.index(a) + attribute = list(hierarchy.keys())[attribute_index] - for a in if_num_list: - for b in then_num_list: - # The attributes slot value from the DNA given 'a' from if_num_list and 'b' from then_num_list: - attribute_slot_value_IF = str(deconstructed_DNA[getAttIndex(hierarchy, a)]) - attribute_slot_value_THEN = str(deconstructed_DNA[getAttIndex(hierarchy, b)]) + for b in hierarchy[attribute]: + if hierarchy[attribute][b]["number"] == a: + a_dna_var = b - if attribute_slot_value_IF in if_num_list[a] and attribute_slot_value_THEN in then_num_list[b]: - violates_rule = True - return violates_rule + if attribute in if_dict: + if a_dna_var in list(if_dict[attribute].keys()): + if_list_selected = True + + # Apply changes in accordance to Variants in 'then_dict' and 'if_list_selected' bool above: + for a in deconstructed_DNA: + attribute_index = deconstructed_DNA.index(a) + attribute = list(hierarchy.keys())[attribute_index] + + if attribute in then_dict: # Check if Attribute from DNA is in 'then_dict' + + # If 'a' is a full Attribute and Variants in if_dict not selected, set 'a' to empty (0): + if list(then_dict[attribute].keys()) == list(hierarchy[attribute].keys()) and not if_list_selected: + deconstructed_DNA[attribute_index] = "0" + + # If Variants in if_dict are selected, set each attribute in 'then_dict' to a random or rarity selected Variant from + # 'then_dict[attribute]' variant_list: + if if_list_selected: + for a in then_dict: + attribute_index = deconstructed_DNA.index(a) + attribute = list(hierarchy.keys())[attribute_index] + + variant_list = list(then_dict[a].keys()) + + if attribute in then_dict: # Check if Attribute from DNA is in 'then_dict' + + number_List_Of_i = [] + rarity_List_Of_i = [] + ifZeroBool = None + variantNum = None + + for b in variant_list: + number = b.split("_")[1] + rarity = b.split("_")[2] + + number_List_Of_i.append(int(number)) + rarity_List_Of_i.append(float(rarity)) + + for b in rarity_List_Of_i: + if b == 0: + ifZeroBool = True + elif b != 0: + ifZeroBool = False + + if enableRarity: + try: + if ifZeroBool: + variantNum = random.choices(number_List_Of_i, k=1) + elif not ifZeroBool: + variantNum = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) + else: + try: + variantNum = random.choices(number_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Attribute collection '{a}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) + deconstructed_DNA[int(attribute_index)] = str(variantNum[0]) + + return deconstructed_DNA + + +# Done, may need minor dictionary looping related changes: +def get_rule_break_type(hierarchy, deconstructed_DNA, if_dict, then_dict): + # Check if Variants in 'if_dict' found in deconstructed_DNA: + if_bool = None # True if Variant in 'deconstructed_DNA' found in 'if_dict' + for a in if_dict: # Attribute in 'if_dict' + for b in if_dict[a]: # Variant in if_dict[Attribute] + var_order_num = str(if_dict[a][b][1]) # Order number of 'b' (Variant) + dna_order_num = str(deconstructed_DNA[if_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA + + if var_order_num == dna_order_num: # If DNA selected Variants found inside IF list variants: + if_bool = True + break + else: if_bool = False + + # Check if Variants in 'then_dict' found in deconstructed_DNA: + full_att_bool = None + then_bool = None # True if Variant in 'deconstructed_DNA' found in 'then_dict' + for a in then_dict: # Attribute in 'then_dict' + for b in then_dict[a]: # Variant in if_dict[Attribute] + var_order_num = str(then_dict[a][b][1]) # Order number of 'b' (Variant) + dna_order_num = str( + deconstructed_DNA[then_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA + + if var_order_num == dna_order_num: # If DNA selected Variants found inside THEN list variants: + if list(then_dict[a].keys()) == list(hierarchy[a].keys()): + full_att_bool = True + then_bool = True + break else: - violates_rule = False - return violates_rule + then_bool = False + + # Rule Bool return summary: + + # If Variants in 'if_dict' found in deconstructed_DNA and Variants in 'then_bool' not found in deconstructed_DNA: + if if_bool and not then_bool: + violates_rule = True + + # If Variants in 'if_dict' not found in deconstructed_DNA, and 'then_dict' variants are found in deconstructed_DNA, + # and they are a part of a full Attribute in 'then_dict' + elif not if_bool and then_bool and full_att_bool: + violates_rule = True + + # If Variants in 'if_dict' not found in deconstructed_DNA, but Variants in 'then_dict' are found in deconstructed_DNA, + # and don't make up a full Attribute: + elif not if_bool and then_bool and not full_att_bool: + violates_rule = False + + else: + violates_rule = False + + return violates_rule, if_bool, then_bool, full_att_bool + +# DONE +def create_dicts(hierarchy, rule_list_items): + structure = { + "attribute1": { + "variant1": [ + "name", + "order_number", + "rarity_number" + "attribute" + "attribute_index" + ], + "variant2": [ + "name", + "order_number", + "rarity_number" + "attribute" + "attribute_index" + ] + }, + "attribute2": { + "variant1": [ + "name", + "order_number", + "rarity_number" + "attribute" + "attribute_index" + ], + "variant2": [ + "name", + "order_number", + "rarity_number" + "attribute" + "attribute_index" + ] + } + } + + def get_var_info(variant): + # Get info for variant dict + name = variant.split("_")[0] + order_number = variant.split("_")[1] + rarity_number = variant.split("_")[2] + attribute = "" + + for a in hierarchy: + for var in list(hierarchy[a].keys()): + if var == variant: + attribute = a + break + attribute_index = list(hierarchy.keys()).index(attribute) + + return [name, order_number, rarity_number, attribute, attribute_index] # list of Var info sent back + + items_returned = collections.defaultdict(dict) + for a in rule_list_items: + for b in hierarchy: + if a == b: # If 'a' is an Attribute, add all 'a' Variants to items_returned dict. + variant_list_of_a = list(hierarchy[a].keys()) + variant_dict_of_a = {} + for c in variant_list_of_a: + variant_dict_of_a[c] = get_var_info(c) + + items_returned[a] = variant_dict_of_a + + if a in list(hierarchy[b].keys()): # If 'a' is a Variant, add all info about that variant to items_returned + items_returned[b][a] = get_var_info(a) + + return dict(items_returned) -# Main Function -def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity): +def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_var_dict): deconstructed_DNA = singleDNA.split("-") didReconstruct = True @@ -161,15 +231,26 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity): didReconstruct = False for rule in logicFile: # Items from 'IF' key for a given rule - if_list = isAttorVar(hierarchy, logicFile[rule]["IF"]) - if_num_list = items_to_num(if_list) + if_dict = create_dicts(hierarchy, logicFile[rule]["IF"]) # Items from 'THEN' key for a given rule - then_list = isAttorVar(hierarchy, logicFile[rule]["THEN"]) - then_num_list = items_to_num(then_list) + then_dict = create_dicts(hierarchy, logicFile[rule]["THEN"]) - if check_if_dna_violates_rules(hierarchy, deconstructed_DNA, if_num_list, then_num_list): - deconstructed_DNA = select_from_then_list(hierarchy, deconstructed_DNA, then_num_list, enableRarity) + # save_result(then_dict) + violates_rule, if_bool, then_bool, full_att_bool = get_rule_break_type(hierarchy, deconstructed_DNA, + if_dict, then_dict) + if deconstructed_DNA[3] != "1": + print(f"\nVIOLATES_RULE:{violates_rule}") + print(f"IF_BOOL:{if_bool}") + print(f"THEN_BOOL:{then_bool}") + print(f"FULL_ATT_BOOL:{full_att_bool}\n") + + if violates_rule: + print(f"======={deconstructed_DNA} VIOLATES RULE======") + + deconstructed_DNA = apply_rules_to_dna( + hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity, excluded_var_dict + ) newDNA = reconstructDNA(deconstructed_DNA) if newDNA != originalDNA: diff --git a/main/Rarity.py b/main/Rarity.py index 606d630..189015b 100644 --- a/main/Rarity.py +++ b/main/Rarity.py @@ -17,7 +17,6 @@ def createDNArarity(hierarchy): for i in hierarchy: number_List_Of_i = [] rarity_List_Of_i = [] - count = 0 ifZeroBool = None for k in hierarchy[i]: @@ -27,8 +26,6 @@ def createDNArarity(hierarchy): rarity = hierarchy[i][k]["rarity"] rarity_List_Of_i.append(float(rarity)) - count += 1 - for x in rarity_List_Of_i: if x == 0: ifZeroBool = True From 3cb2a69c81932e5b882b69c4c4bfbdd153a2dfee Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:04:25 -0400 Subject: [PATCH 03/12] Changes to Materials and Logic - Added AutoSave before generation functionality to Other panel - Removed strip_empty_exclude function - Modified Exporter to work with new Material Randomizer layout - Added Enable Materials option in logicafyDNAsingle function in preparation for Material Randomizer Logic - Materials are no longer formatted like Variants, the Material List is now a dictionary where the keys are the names of the Materials and the values are the Rarity percentages. Each Variant in the Materials.json file can have a different list with different rarity values that are selected for only that variant. Material file format: ``` { "Variant Name": { "Material List": { "Material Name 1": 90, "Material Name 2": 5, "Material Name 3": 1, "Material Name 4": 4 }, "Variant Objects": [] } } ``` --- __init__.py | 14 ++++++++++ main/DNA_Generator.py | 46 +++++++-------------------------- main/Exporter.py | 10 ++++--- main/Logic.py | 7 +++-- main/Material_Generator.py | 53 ++++++++++++++++++++++---------------- 5 files changed, 63 insertions(+), 67 deletions(-) diff --git a/__init__.py b/__init__.py index 3390b9f..77d252b 100644 --- a/__init__.py +++ b/__init__.py @@ -376,6 +376,8 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): ) # Other Panel: + enableAutoSave: bpy.props.BoolProperty(name="Auto Save Before Generation") + # API Panel properties: apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') @@ -478,6 +480,11 @@ class exportNFTs(bpy.types.Operator): name="Reverse Order") def execute(self, context): + + enableAutoSave = bpy.context.scene.input_tool.enableAutoSave + if enableAutoSave: + bpy.ops.wm.save_mainfile() + class input: nftName = bpy.context.scene.input_tool.nftName save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path) @@ -923,10 +930,17 @@ class BMNFTS_PT_Other(bpy.types.Panel): input_tool_scene = scene.input_tool """ + Other: + A place to store miscellaneous settings, features, and external links that the user may find useful but doesn't + want to get in the way of their work flow. Export Settings: This panel gives the user the option to export all settings from the Blend_My_NFTs addon into a config file. Settings will be read from the config file when running heedlessly. """ + + row = layout.row() + row.prop(input_tool_scene, "enableAutoSave") + layout.label(text=f"Running Blend_My_NFTs Headless:") save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path) diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index bcb6f70..838a39f 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -123,47 +123,18 @@ def get_hierarchy(): return hierarchy -def strip_empty_exclude(hierarchy): - """ - Strips Empty Exclude variants from the hierarchy. - """ - excluded_var_dict = {} - - for a in hierarchy: - empty_variant = "" - empty_var_count = 0 - variant_list = list(hierarchy[a].keys()) - # empty_var_count and raise() prevents this for from causing breaking stuff: - - for b in variant_list: - if b.split("_")[1] == "0": - empty_variant = b - empty_var_count += 1 - if empty_var_count > 1: - raise Exception( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"The Attribute collection '{a}' has more than one Empty variant.\n" - f"Attributes can only have a maximum of 1 Empty variant, please review the documentation here:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" - ) - - if len(empty_variant.split("_")) == 4 and empty_variant.split("_")[3] == "Exclude": - excluded_var_dict[a] = empty_variant - del hierarchy[a][empty_variant] - - return hierarchy, excluded_var_dict def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile): """ Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList. """ - hierarchy, excluded_var_dict = strip_empty_exclude(get_hierarchy()) + hierarchy = get_hierarchy() # DNA random, Rarity and Logic methods: DataDictionary = {} - def createDNArandom(): + def createDNArandom(hierarchy): """Creates a single DNA randomly without Rarity or Logic.""" dnaStr = "" dnaStrList = [] @@ -195,20 +166,22 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable singleDNA = "" # Comments for debugging random, rarity, logic, and materials. if not enableRarity: - singleDNA = createDNArandom() + singleDNA = createDNArandom(hierarchy) # print("============") # print(f"Original DNA: {singleDNA}") if enableRarity: singleDNA = Rarity.createDNArarity(hierarchy) # print(f"Rarity DNA: {singleDNA}") + if enableMaterials: + singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile, enableRarity) + # print(f"Materials DNA: {singleDNA}") + if enableLogic: - singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_var_dict) + singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMaterials) # print(f"Logic DNA: {singleDNA}") - if enableMaterials: - singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile) - # print(f"Materials DNA: {singleDNA}") + # print("============\n") return singleDNA @@ -246,7 +219,6 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable # Data stored in batchDataDictionary: DataDictionary["numNFTsGenerated"] = len(DNAList) - DataDictionary["excludedVariants"] = excluded_var_dict DataDictionary["hierarchy"] = hierarchy DataDictionary["DNAList"] = DNAList diff --git a/main/Exporter.py b/main/Exporter.py index d1bb513..f77bb52 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -177,12 +177,14 @@ def render_and_save_NFTs(input): if hierarchy[attribute][var]['number'] == variant: variant = var - if material != '0': + if material != '0': # If material is not empty for variant_m in materialsFile: if variant == variant_m: - for mat in materialsFile[variant_m]["Material List"]: - if mat.split('_')[1] == material: - material = mat + # Getting Materials name from Materials index in the Materials List + materials_list = list(materialsFile[variant_m]["Material List"].keys()) + + material = materials_list[int(material) - 1] # Subtract 1 because '0' means empty mat + break full_dna_dict[variant] = material diff --git a/main/Logic.py b/main/Logic.py index 230c80d..eef2c0a 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -15,7 +15,7 @@ def reconstructDNA(deconstructedDNA): return ''.join(reconstructed_DNA.split('-', 1)) -def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity, excluded_var_dict): +def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity): # Check if Variants in if_dict are in deconstructed_DNA, if so return if_list_selected = True: if_list_selected = False @@ -221,7 +221,7 @@ def create_dicts(hierarchy, rule_list_items): return dict(items_returned) -def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_var_dict): +def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMaterials): deconstructed_DNA = singleDNA.split("-") didReconstruct = True @@ -236,7 +236,6 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_va # Items from 'THEN' key for a given rule then_dict = create_dicts(hierarchy, logicFile[rule]["THEN"]) - # save_result(then_dict) violates_rule, if_bool, then_bool, full_att_bool = get_rule_break_type(hierarchy, deconstructed_DNA, if_dict, then_dict) if deconstructed_DNA[3] != "1": @@ -249,7 +248,7 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, excluded_va print(f"======={deconstructed_DNA} VIOLATES RULE======") deconstructed_DNA = apply_rules_to_dna( - hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity, excluded_var_dict + hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity ) newDNA = reconstructDNA(deconstructed_DNA) diff --git a/main/Material_Generator.py b/main/Material_Generator.py index cb02756..f921a07 100644 --- a/main/Material_Generator.py +++ b/main/Material_Generator.py @@ -9,34 +9,42 @@ import json import random -def select_material(materialList): +def select_material(materialList, enableRarity): """Selects a material from a passed material list. """ - - number_List_Of_i = [] + material_List_Of_i = [] # List of Material names instead of order numbers rarity_List_Of_i = [] - ifZeroBool = None - for material in materialList: + # Material Order Number comes from index in the Material List in materials.json for a given Variant. + # material_order_num = list(materialList.keys()).index(material) - material_order_num = material.split("_")[1] - number_List_Of_i.append(material_order_num) + material_List_Of_i.append(material) - material_rarity_percent = material.split("_")[1] + material_rarity_percent = materialList[material] rarity_List_Of_i.append(float(material_rarity_percent)) - for x in rarity_List_Of_i: - if x == 0: - ifZeroBool = True - break - elif x != 0: - ifZeroBool = False + print(f"MATERIAL_LIST_OF_I:{material_List_Of_i}") + print(f"RARITY_LIST_OF_I:{rarity_List_Of_i}") - if ifZeroBool: - selected_material = random.choices(number_List_Of_i, k=1) - elif not ifZeroBool: - selected_material = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1) + if enableRarity: + ifZeroBool = None - return selected_material[0] + for x in rarity_List_Of_i: + if x == 0: + ifZeroBool = True + break + elif x != 0: + ifZeroBool = False + + if ifZeroBool: + selected_material = random.choices(material_List_Of_i, k=1) + elif not ifZeroBool: + + selected_material = random.choices(material_List_Of_i, weights=rarity_List_Of_i, k=1) + + else: + selected_material = random.choices(material_List_Of_i, k=1) + + return selected_material[0], materialList def get_variant_att_index(variant, hierarchy): variant_attribute = None @@ -69,7 +77,7 @@ def match_DNA_to_Variant(hierarchy, singleDNA): dnaDictionary.update({x: k}) return dnaDictionary -def apply_materials(hierarchy, singleDNA, materialsFile): +def apply_materials(hierarchy, singleDNA, materialsFile, enableRarity): """ DNA with applied material example: "1-1:1-1" : @@ -85,8 +93,9 @@ def apply_materials(hierarchy, singleDNA, materialsFile): complete = False for b in materialsFile: if singleDNADict[a] == b: - mat = select_material(materialsFile[b]['Material List']) - deconstructed_MaterialDNA[a] = mat + material_name, materialList, = select_material(materialsFile[b]['Material List'], enableRarity) + material_order_num = list(materialList.keys()).index(material_name) # Gets the Order Number of the Material + deconstructed_MaterialDNA[a] = str(material_order_num + 1) complete = True if not complete: deconstructed_MaterialDNA[a] = "0" From b4ecfe74513e8e42b08283692d15925c0605a9b7 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:12:27 -0400 Subject: [PATCH 04/12] Changing Material Rarity and adding error message --- main/Material_Generator.py | 50 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/main/Material_Generator.py b/main/Material_Generator.py index f921a07..9358aae 100644 --- a/main/Material_Generator.py +++ b/main/Material_Generator.py @@ -7,12 +7,15 @@ import bpy import json import random +from .Constants import bcolors, removeList, remove_file_by_extension, save_result -def select_material(materialList, enableRarity): +def select_material(materialList, variant, enableRarity): """Selects a material from a passed material list. """ material_List_Of_i = [] # List of Material names instead of order numbers rarity_List_Of_i = [] + ifZeroBool = None + for material in materialList: # Material Order Number comes from index in the Material List in materials.json for a given Variant. # material_order_num = list(materialList.keys()).index(material) @@ -25,24 +28,35 @@ def select_material(materialList, enableRarity): print(f"MATERIAL_LIST_OF_I:{material_List_Of_i}") print(f"RARITY_LIST_OF_I:{rarity_List_Of_i}") + for b in rarity_List_Of_i: + if b == 0: + ifZeroBool = True + elif b != 0: + ifZeroBool = False + if enableRarity: - ifZeroBool = None - - for x in rarity_List_Of_i: - if x == 0: - ifZeroBool = True - break - elif x != 0: - ifZeroBool = False - - if ifZeroBool: - selected_material = random.choices(material_List_Of_i, k=1) - elif not ifZeroBool: - - selected_material = random.choices(material_List_Of_i, weights=rarity_List_Of_i, k=1) - + try: + if ifZeroBool: + selected_material = random.choices(material_List_Of_i, k=1) + elif not ifZeroBool: + selected_material = random.choices(material_List_Of_i, weights=rarity_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Material List of the Variant collection '{variant}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) else: - selected_material = random.choices(material_List_Of_i, k=1) + try: + selected_material = random.choices(material_List_Of_i, k=1) + except IndexError: + raise IndexError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"An issue was found within the Material List of the Variant collection '{variant}'. For more information on Blend_My_NFTs compatible scenes, " + f"see:\n{bcolors.RESET}" + f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" + ) return selected_material[0], materialList @@ -93,7 +107,7 @@ def apply_materials(hierarchy, singleDNA, materialsFile, enableRarity): complete = False for b in materialsFile: if singleDNADict[a] == b: - material_name, materialList, = select_material(materialsFile[b]['Material List'], enableRarity) + material_name, materialList, = select_material(materialsFile[b]['Material List'], b, enableRarity) material_order_num = list(materialList.keys()).index(material_name) # Gets the Order Number of the Material deconstructed_MaterialDNA[a] = str(material_order_num + 1) complete = True From a71d7982f9830e585a596df594df45f05f827bf4 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Wed, 8 Jun 2022 15:11:39 -0400 Subject: [PATCH 05/12] Updating commit date and version --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 77d252b..cc6a1c4 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,15 @@ bl_info = { "name": "Blend_My_NFTs", "author": "Torrin Leonard, This Cozy Studio Inc", - "version": (4, 0, 2), + "version": (4, 1, 0), "blender": (3, 2, 0), "location": "View3D", "description": "An open source, free to use Blender add-on that enables you to create thousands of unique images, animations, and 3D models.", "category": "Development", } -BMNFTS_VERSION = "v4.0.2" -LAST_UPDATED = "8:19AM, May 31st, 2022" +BMNFTS_VERSION = "v4.1.0" +LAST_UPDATED = "3:08PM, June 8th, 2022" # ======== Import handling ======== # From 50b869fe091f2cc6d97ccea69d70f0c22ad584ef Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:05:51 -0400 Subject: [PATCH 06/12] Removing problematic test code --- __init__.py | 6 +++--- main/Logic.py | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/__init__.py b/__init__.py index cc6a1c4..e3a34eb 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,15 @@ bl_info = { "name": "Blend_My_NFTs", "author": "Torrin Leonard, This Cozy Studio Inc", - "version": (4, 1, 0), + "version": (4, 2, 0), "blender": (3, 2, 0), "location": "View3D", "description": "An open source, free to use Blender add-on that enables you to create thousands of unique images, animations, and 3D models.", "category": "Development", } -BMNFTS_VERSION = "v4.1.0" -LAST_UPDATED = "3:08PM, June 8th, 2022" +BMNFTS_VERSION = "v4.2.0" +LAST_UPDATED = "11:05PM, June 8th, 2022" # ======== Import handling ======== # diff --git a/main/Logic.py b/main/Logic.py index eef2c0a..34c2fd4 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -238,12 +238,6 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMater violates_rule, if_bool, then_bool, full_att_bool = get_rule_break_type(hierarchy, deconstructed_DNA, if_dict, then_dict) - if deconstructed_DNA[3] != "1": - print(f"\nVIOLATES_RULE:{violates_rule}") - print(f"IF_BOOL:{if_bool}") - print(f"THEN_BOOL:{then_bool}") - print(f"FULL_ATT_BOOL:{full_att_bool}\n") - if violates_rule: print(f"======={deconstructed_DNA} VIOLATES RULE======") From 472d942b9f6f4bb2ffdba05728303c1d3629baf1 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sat, 11 Jun 2022 08:48:09 -0400 Subject: [PATCH 07/12] Fixing small issue with Logic --- __init__.py | 8 ++++---- main/Logic.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index e3a34eb..2c3f6a8 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,15 @@ bl_info = { "name": "Blend_My_NFTs", "author": "Torrin Leonard, This Cozy Studio Inc", - "version": (4, 2, 0), + "version": (4, 3, 0), "blender": (3, 2, 0), "location": "View3D", - "description": "An open source, free to use Blender add-on that enables you to create thousands of unique images, animations, and 3D models.", + "description": "A free and opensource Blender add-on that enables you to create thousands of unique images, animations, and 3D models.", "category": "Development", } -BMNFTS_VERSION = "v4.2.0" -LAST_UPDATED = "11:05PM, June 8th, 2022" +BMNFTS_VERSION = "v4.3.0" +LAST_UPDATED = "8:45AM, June 11th, 2022" # ======== Import handling ======== # diff --git a/main/Logic.py b/main/Logic.py index 34c2fd4..66489e4 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -46,7 +46,7 @@ def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableR # 'then_dict[attribute]' variant_list: if if_list_selected: for a in then_dict: - attribute_index = deconstructed_DNA.index(a) + attribute_index = list(hierarchy.keys()).index(a) attribute = list(hierarchy.keys())[attribute_index] variant_list = list(then_dict[a].keys()) From f32f2baf3cd0d8504e83295eaeabc738060c1c1c Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sat, 18 Jun 2022 14:21:25 -0400 Subject: [PATCH 08/12] Adding Auto shutdown functionality --- __init__.py | 42 +++++++++++++++++++++++++++++++++++++++++- main/Exporter.py | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 2c3f6a8..a2aac32 100644 --- a/__init__.py +++ b/__init__.py @@ -23,6 +23,7 @@ import os import sys import json import importlib +from datetime import datetime, timezone # "a little hacky bs" - Matthew TheBrochacho ;) sys.path.append(os.path.dirname(os.path.realpath(__file__))) @@ -70,6 +71,7 @@ if "bpy" in locals(): # Used for updating text and buttons in UI panels combinations: int = 0 recommended_limit: int = 0 +dt = datetime.now(timezone.utc).astimezone() # Date Time in UTC local @persistent @@ -376,7 +378,15 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): ) # Other Panel: - enableAutoSave: bpy.props.BoolProperty(name="Auto Save Before Generation") + enableAutoSave: bpy.props.BoolProperty(name="Auto Save Before Generation", description="Automatically saves your Blender file when 'Generate NFTs & Create Metadata' button is clicked") + + # Auto Shutodwn: + enableAutoShutdown: bpy.props.BoolProperty(name="Auto Shutdown", description="Automatically shuts down your computer after a Batch is finished Generating") + + specify_timeBool: bpy.props.BoolProperty(name="Shutdown in a Given Amount of Time", description="Wait a given amount of time after a Batch is generated before Automatic Shutdown") + hours: bpy.props.IntProperty(default=0, min=0) + minutes: bpy.props.IntProperty(default=0, min=0) + # API Panel properties: @@ -516,6 +526,12 @@ class exportNFTs(bpy.types.Operator): enableMaterials = bpy.context.scene.input_tool.enableMaterials materialsFile = bpy.path.abspath(bpy.context.scene.input_tool.materialsFile) + enableAutoShutdown = bpy.context.scene.input_tool.enableAutoShutdown + + specify_timeBool = bpy.context.scene.input_tool.specify_timeBool + hours = bpy.context.scene.input_tool.hours + minutes = bpy.context.scene.input_tool.minutes + # fail state variables, set to no fail due to resume_failed_batch() Operator in BMNFTS_PT_GenerateNFTs Panel fail_state = False failed_batch = None @@ -941,6 +957,30 @@ class BMNFTS_PT_Other(bpy.types.Panel): row = layout.row() row.prop(input_tool_scene, "enableAutoSave") + # Auto Shutdown: + row = layout.row() + row.prop(input_tool_scene, "enableAutoShutdown") + row.label(text="*Must Run Blender as Admin") + + if bpy.context.scene.input_tool.enableAutoShutdown: + row = layout.row() + row.prop(input_tool_scene, "specify_timeBool") + + time_row1 = layout.row() + time_row1.label(text=f"Hours") + time_row1.prop(input_tool_scene, "hours", text="") + + time_row2 = layout.row() + time_row2.label(text=f"Minutes") + time_row2.prop(input_tool_scene, "minutes", text="") + + if not bpy.context.scene.input_tool.specify_timeBool: + time_row1.enabled = False + time_row2.enabled = False + else: + time_row1.enabled = True + time_row2.enabled = True + layout.label(text=f"Running Blend_My_NFTs Headless:") save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path) diff --git a/main/Exporter.py b/main/Exporter.py index f77bb52..45ee41c 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -7,6 +7,7 @@ import os import time import json import datetime +import platform from .loading_animation import Loader from .Constants import bcolors, removeList, remove_file_by_extension from .Metadata import createCardanoMetadata, createSolanaMetaData, createErc721MetaData @@ -112,18 +113,20 @@ def render_and_save_NFTs(input): Renders the NFT DNA in a Batch#.json, where # is renderBatch in config.py. Turns off the viewport camera and the render camera for all items in hierarchy. """ - print(f"\nFAILED BATCH = {input.failed_batch}\n") - print(f"\nBATCH TO GENERATE = {input.batchToGenerate}\n") time_start_1 = time.time() + # If failed Batch is detected and user is resuming its generation: if input.fail_state: + print(f"{bcolors.ERROR}\nResuming Failed Batch {input.failed_batch}\n{bcolors.RESET}") NFTs_in_Batch, hierarchy, BatchDNAList = getBatchData(input.failed_batch, input.batch_json_save_path) for a in range(input.failed_dna): del BatchDNAList[0] x = input.failed_dna + 1 + # If user is generating the normal way: else: + print(f"\nGenerating Batch {input.batchToGenerate}\n") NFTs_in_Batch, hierarchy, BatchDNAList = getBatchData(input.batchToGenerate, input.batch_json_save_path) save_generation_state(input) x = 1 @@ -459,3 +462,28 @@ def render_and_save_NFTs(input): batch_infoFolder = os.path.join(input.nftBatch_save_path, "Batch" + str(input.batchToGenerate), "batch_info.json") save_batch(batch_info, batch_infoFolder) + + # Automatic Shutdown: + # If user selects automatic shutdown but did not specify time after Batch completion + + def shutdown(time): + plateform = platform.system() + + if plateform == "Windows": + os.system(f"shutdown /s /t {time}") + if plateform == "Darwin": + os.system(f"shutdown /s /t {time}") + + if input.enableAutoShutdown and not input.specify_timeBool: + shutdown(0) + + # If user selects automatic shutdown and specify time after Batch completion + if input.enableAutoShutdown and input.specify_timeBool: + + hours = (int(input.hours)/60)/60 + minutes = int(input.minutes)/60 + total_sleep_time = hours + minutes + + # time.sleep(total_sleep_time) + + shutdown(total_sleep_time) From 68fb0e245cae88737f770e2a0d5fc13621ae9395 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sat, 18 Jun 2022 14:25:28 -0400 Subject: [PATCH 09/12] Update __init__.py --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index a2aac32..44ec9ac 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,15 @@ bl_info = { "name": "Blend_My_NFTs", "author": "Torrin Leonard, This Cozy Studio Inc", - "version": (4, 3, 0), + "version": (4, 4, 0), "blender": (3, 2, 0), "location": "View3D", "description": "A free and opensource Blender add-on that enables you to create thousands of unique images, animations, and 3D models.", "category": "Development", } -BMNFTS_VERSION = "v4.3.0" -LAST_UPDATED = "8:45AM, June 11th, 2022" +BMNFTS_VERSION = "v4.4.0" +LAST_UPDATED = "2:25PM, June 18th, 2022" # ======== Import handling ======== # From 082edaf4e71109ddb245028a5ee598fbe72cd9f7 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sat, 18 Jun 2022 20:29:02 -0400 Subject: [PATCH 10/12] Adding the ability to send emails when Batch is complete - Reverted Materials over Logic change in DNA_Generator.py --- __init__.py | 25 +++++++++++++++++++++++++ main/DNA_Generator.py | 7 +++---- main/Exporter.py | 41 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/__init__.py b/__init__.py index 44ec9ac..1f6c969 100644 --- a/__init__.py +++ b/__init__.py @@ -387,6 +387,11 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): hours: bpy.props.IntProperty(default=0, min=0) minutes: bpy.props.IntProperty(default=0, min=0) + # Send Batch Complete Email: + emailNotificationBool: bpy.props.BoolProperty(name="Email Notifications", description="Receive Email Notifications from Blender once a batch is finished generating") + sender_from: bpy.props.StringProperty(name="From", default="from@example.com") + email_password: bpy.props.StringProperty(name="Password", subtype='PASSWORD') + receiver_to: bpy.props.StringProperty(name="To", default="to@example.com") # API Panel properties: @@ -532,6 +537,11 @@ class exportNFTs(bpy.types.Operator): hours = bpy.context.scene.input_tool.hours minutes = bpy.context.scene.input_tool.minutes + emailNotificationBool = bpy.context.scene.input_tool.emailNotificationBool + sender_from = bpy.context.scene.input_tool.sender_from + email_password = bpy.context.scene.input_tool.email_password + receiver_to = bpy.context.scene.input_tool.receiver_to + # fail state variables, set to no fail due to resume_failed_batch() Operator in BMNFTS_PT_GenerateNFTs Panel fail_state = False failed_batch = None @@ -980,7 +990,22 @@ class BMNFTS_PT_Other(bpy.types.Panel): else: time_row1.enabled = True time_row2.enabled = True + layout.separator() + row = layout.row() + row.prop(input_tool_scene, "emailNotificationBool") + + if bpy.context.scene.input_tool.emailNotificationBool: + row = layout.row() + row.prop(input_tool_scene, "sender_from") + row = layout.row() + row.prop(input_tool_scene, "email_password") + + layout.separator() + row = layout.row() + row.prop(input_tool_scene, "receiver_to") + + layout.separator() layout.label(text=f"Running Blend_My_NFTs Headless:") save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path) diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 838a39f..613b97c 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -173,14 +173,13 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable singleDNA = Rarity.createDNArarity(hierarchy) # print(f"Rarity DNA: {singleDNA}") - if enableMaterials: - singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile, enableRarity) - # print(f"Materials DNA: {singleDNA}") - if enableLogic: singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMaterials) # print(f"Logic DNA: {singleDNA}") + if enableMaterials: + singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile, enableRarity) + # print(f"Materials DNA: {singleDNA}") # print("============\n") diff --git a/main/Exporter.py b/main/Exporter.py index 45ee41c..bb4b8e2 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -4,8 +4,10 @@ import bpy import os +import ssl import time import json +import smtplib import datetime import platform from .loading_animation import Loader @@ -463,9 +465,44 @@ def render_and_save_NFTs(input): batch_infoFolder = os.path.join(input.nftBatch_save_path, "Batch" + str(input.batchToGenerate), "batch_info.json") save_batch(batch_info, batch_infoFolder) + # Send Email that Batch is complete: + port = 465 # For SSL + smtp_server = "smtp.gmail.com" + sender_email = input.sender_from # Enter your address + receiver_email = input.receiver_to # Enter receiver address + password = input.email_password + + # Get batch info for message: + if input.fail_state: + batch = input.fail_state + batchData = getBatchData(input.failed_batch, input.batch_json_save_path) + + else: + batchData = getBatchData(input.batchToGenerate, input.batch_json_save_path) + + batch = input.batchToGenerate + + generation_time = str(datetime.timedelta(seconds=batch_complete_time)) + + message = f"""\ + Subject: Blend_My_NFTs: Batch {batch} completed {x - 1} NFTs in {generation_time} (h:m:s) + + Generation Time: + {generation_time.split(':')[0]} Hours, {generation_time.split(':')[1]} Minutes, {generation_time.split(':')[2]} Seconds + Batch Data: + + {batchData} + + This message was sent from an instance of the Blend_My_NFTs Blender add-on. + """ + + context = ssl.create_default_context() + with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: + server.login(sender_email, password) + server.sendmail(sender_email, receiver_email, message) + # Automatic Shutdown: # If user selects automatic shutdown but did not specify time after Batch completion - def shutdown(time): plateform = platform.system() @@ -486,4 +523,4 @@ def render_and_save_NFTs(input): # time.sleep(total_sleep_time) - shutdown(total_sleep_time) + shutdown(total_sleep_time) \ No newline at end of file From e449ee3bdf2bb71a1122b70c628af6ca0799cb1f Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Sat, 18 Jun 2022 21:03:17 -0400 Subject: [PATCH 11/12] Small change --- main/Exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/Exporter.py b/main/Exporter.py index bb4b8e2..01fd1eb 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -485,7 +485,7 @@ def render_and_save_NFTs(input): generation_time = str(datetime.timedelta(seconds=batch_complete_time)) message = f"""\ - Subject: Blend_My_NFTs: Batch {batch} completed {x - 1} NFTs in {generation_time} (h:m:s) + Subject: Batch {batch} completed {x - 1} NFTs in {generation_time} (h:m:s) Generation Time: {generation_time.split(':')[0]} Hours, {generation_time.split(':')[1]} Minutes, {generation_time.split(':')[2]} Seconds From f0ba77f7a49a5a9ccd3250abcaf309de109041d1 Mon Sep 17 00:00:00 2001 From: Torrin Leonard <82110564+torrinworx@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:32:49 -0400 Subject: [PATCH 12/12] Added "NOT" rule to Logic - Email is now only activated if the user specifies - Implemented "NOT" rule in Logic.py - Added documentation for Synced Attribute Material values in Material_Generator.py --- __init__.py | 1 + main/DNA_Generator.py | 4 +- main/Exporter.py | 57 +++++++------- main/Logic.py | 157 ++++++++++++++++++++++--------------- main/Material_Generator.py | 9 +++ 5 files changed, 136 insertions(+), 92 deletions(-) diff --git a/__init__.py b/__init__.py index 1f6c969..868bc7f 100644 --- a/__init__.py +++ b/__init__.py @@ -994,6 +994,7 @@ class BMNFTS_PT_Other(bpy.types.Panel): row = layout.row() row.prop(input_tool_scene, "emailNotificationBool") + row.label(text="*Windows 10+ only") if bpy.context.scene.input_tool.emailNotificationBool: row = layout.row() diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 613b97c..d045e2f 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -16,8 +16,8 @@ from .Constants import bcolors, removeList, remove_file_by_extension def get_hierarchy(): """ - Returns the hierarchy of a given Blender scene. - """ + Returns the hierarchy of a given Blender scene. + """ coll = bpy.context.scene.collection diff --git a/main/Exporter.py b/main/Exporter.py index 01fd1eb..99a7c15 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -466,40 +466,41 @@ def render_and_save_NFTs(input): save_batch(batch_info, batch_infoFolder) # Send Email that Batch is complete: - port = 465 # For SSL - smtp_server = "smtp.gmail.com" - sender_email = input.sender_from # Enter your address - receiver_email = input.receiver_to # Enter receiver address - password = input.email_password + if input.emailNotificationBool: + port = 465 # For SSL + smtp_server = "smtp.gmail.com" + sender_email = input.sender_from # Enter your address + receiver_email = input.receiver_to # Enter receiver address + password = input.email_password - # Get batch info for message: - if input.fail_state: - batch = input.fail_state - batchData = getBatchData(input.failed_batch, input.batch_json_save_path) + # Get batch info for message: + if input.fail_state: + batch = input.fail_state + batchData = getBatchData(input.failed_batch, input.batch_json_save_path) - else: - batchData = getBatchData(input.batchToGenerate, input.batch_json_save_path) + else: + batchData = getBatchData(input.batchToGenerate, input.batch_json_save_path) - batch = input.batchToGenerate + batch = input.batchToGenerate - generation_time = str(datetime.timedelta(seconds=batch_complete_time)) + generation_time = str(datetime.timedelta(seconds=batch_complete_time)) - message = f"""\ - Subject: Batch {batch} completed {x - 1} NFTs in {generation_time} (h:m:s) - - Generation Time: - {generation_time.split(':')[0]} Hours, {generation_time.split(':')[1]} Minutes, {generation_time.split(':')[2]} Seconds - Batch Data: - - {batchData} - - This message was sent from an instance of the Blend_My_NFTs Blender add-on. - """ + message = f"""\ + Subject: Batch {batch} completed {x - 1} NFTs in {generation_time} (h:m:s) + + Generation Time: + {generation_time.split(':')[0]} Hours, {generation_time.split(':')[1]} Minutes, {generation_time.split(':')[2]} Seconds + Batch Data: + + {batchData} + + This message was sent from an instance of the Blend_My_NFTs Blender add-on. + """ - context = ssl.create_default_context() - with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: - server.login(sender_email, password) - server.sendmail(sender_email, receiver_email, message) + context = ssl.create_default_context() + with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: + server.login(sender_email, password) + server.sendmail(sender_email, receiver_email, message) # Automatic Shutdown: # If user selects automatic shutdown but did not specify time after Batch completion diff --git a/main/Logic.py b/main/Logic.py index 66489e4..0335f9e 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -7,6 +7,7 @@ import collections from .Constants import bcolors, removeList, remove_file_by_extension, save_result + def reconstructDNA(deconstructedDNA): reconstructed_DNA = "" for a in deconstructedDNA: @@ -14,9 +15,24 @@ def reconstructDNA(deconstructedDNA): reconstructed_DNA += num return ''.join(reconstructed_DNA.split('-', 1)) +def get_var_info(variant, hierarchy): + # Get info for variant dict + name = variant.split("_")[0] + order_number = variant.split("_")[1] + rarity_number = variant.split("_")[2] + attribute = "" -def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity): + for a in hierarchy: + for var in list(hierarchy[a].keys()): + if var == variant: + attribute = a + break + attribute_index = list(hierarchy.keys()).index(attribute) + return [name, order_number, rarity_number, attribute, attribute_index] # list of Var info sent back + + +def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, result_dict, result_dict_type, enableRarity): # Check if Variants in if_dict are in deconstructed_DNA, if so return if_list_selected = True: if_list_selected = False for a in deconstructed_DNA: @@ -31,27 +47,49 @@ def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableR if a_dna_var in list(if_dict[attribute].keys()): if_list_selected = True - # Apply changes in accordance to Variants in 'then_dict' and 'if_list_selected' bool above: + + # Apply changes in accordance to Variants in 'result_dict' and 'if_list_selected' bool above: for a in deconstructed_DNA: attribute_index = deconstructed_DNA.index(a) attribute = list(hierarchy.keys())[attribute_index] - if attribute in then_dict: # Check if Attribute from DNA is in 'then_dict' + if attribute in result_dict: # Check if Attribute from DNA is in 'result_dict' # If 'a' is a full Attribute and Variants in if_dict not selected, set 'a' to empty (0): - if list(then_dict[attribute].keys()) == list(hierarchy[attribute].keys()) and not if_list_selected: + if list(result_dict[attribute].keys()) == list(hierarchy[attribute].keys()) and not if_list_selected: deconstructed_DNA[attribute_index] = "0" - # If Variants in if_dict are selected, set each attribute in 'then_dict' to a random or rarity selected Variant from - # 'then_dict[attribute]' variant_list: + # If 'a' is a full Attribute and result_dict_type = "NOT", set 'a' to empty (0): + if list(result_dict[attribute].keys()) == list( + hierarchy[attribute].keys()) and if_list_selected and result_dict_type == "NOT": + deconstructed_DNA[attribute_index] = "0" + + # If Variants in if_dict are selected, set each attribute in 'result_dict' to a random or rarity selected Variant from + # 'result_dict[attribute]' variant_list: if if_list_selected: - for a in then_dict: + + # Invert 'items_returned' if 'NOT' rule is selected: + if result_dict_type == "NOT": + for a in result_dict: + var_selected_list = list(result_dict[a].keys()) # list of variants from 'NOT' + att_selected_list = list(hierarchy[a].keys()) # full list of variants from hierarchy attribute + + # If 'a' is not a full Attribute, invert the variants: + if len(var_selected_list) != len(att_selected_list): + var_selected_list = [i for i in att_selected_list if i not in var_selected_list] + + var_selected_list_complete = {} + for i in var_selected_list: + var_selected_list_complete[i] = get_var_info(i, hierarchy) + result_dict[a] = var_selected_list_complete + + for a in result_dict: attribute_index = list(hierarchy.keys()).index(a) attribute = list(hierarchy.keys())[attribute_index] - variant_list = list(then_dict[a].keys()) + variant_list = list(result_dict[a].keys()) - if attribute in then_dict: # Check if Attribute from DNA is in 'then_dict' + if attribute in result_dict: # Check if Attribute from DNA is in 'then_dict' number_List_Of_i = [] rarity_List_Of_i = [] @@ -99,60 +137,63 @@ def apply_rules_to_dna(hierarchy, deconstructed_DNA, if_dict, then_dict, enableR return deconstructed_DNA -# Done, may need minor dictionary looping related changes: -def get_rule_break_type(hierarchy, deconstructed_DNA, if_dict, then_dict): +def get_rule_break_type(hierarchy, deconstructed_DNA, if_dict, result_dict, result_dict_type): # Check if Variants in 'if_dict' found in deconstructed_DNA: - if_bool = None # True if Variant in 'deconstructed_DNA' found in 'if_dict' + if_bool = False # True if Variant in 'deconstructed_DNA' found in 'if_dict' for a in if_dict: # Attribute in 'if_dict' for b in if_dict[a]: # Variant in if_dict[Attribute] var_order_num = str(if_dict[a][b][1]) # Order number of 'b' (Variant) - dna_order_num = str(deconstructed_DNA[if_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA + dna_order_num = str( + deconstructed_DNA[if_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA if var_order_num == dna_order_num: # If DNA selected Variants found inside IF list variants: if_bool = True break - else: if_bool = False + else: + continue + break - # Check if Variants in 'then_dict' found in deconstructed_DNA: - full_att_bool = None - then_bool = None # True if Variant in 'deconstructed_DNA' found in 'then_dict' - for a in then_dict: # Attribute in 'then_dict' - for b in then_dict[a]: # Variant in if_dict[Attribute] - var_order_num = str(then_dict[a][b][1]) # Order number of 'b' (Variant) + # Check if Variants in 'result_dict' found in deconstructed_DNA: + full_att_bool = False + result_bool = False # True if Variant in 'deconstructed_DNA' found in 'result_dict' + for a in result_dict: # Attribute in 'result_dict' + for b in result_dict[a]: # Variant in if_dict[Attribute] + var_order_num = str(result_dict[a][b][1]) # Order number of 'b' (Variant) dna_order_num = str( - deconstructed_DNA[then_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA - + deconstructed_DNA[result_dict[a][b][4]]) # Order Number of 'b's attribute in deconstructed_DNA if var_order_num == dna_order_num: # If DNA selected Variants found inside THEN list variants: - if list(then_dict[a].keys()) == list(hierarchy[a].keys()): + if list(result_dict[a].keys()) == list(hierarchy[a].keys()): full_att_bool = True - then_bool = True + result_bool = True break - else: - then_bool = False + else: + continue + break # Rule Bool return summary: + violates_rule = False - # If Variants in 'if_dict' found in deconstructed_DNA and Variants in 'then_bool' not found in deconstructed_DNA: - if if_bool and not then_bool: + # If Variants in 'if_dict' found in deconstructed_DNA and Variants in 'result_dict' not found in deconstructed_DNA: + if if_bool and not result_bool: violates_rule = True - # If Variants in 'if_dict' not found in deconstructed_DNA, and 'then_dict' variants are found in deconstructed_DNA, + elif if_bool and result_bool and result_dict_type == "NOT": + violates_rule = True + + # If Variants in 'if_dict' not found in deconstructed_DNA, and 'result_dict' variants are found in deconstructed_DNA, # and they are a part of a full Attribute in 'then_dict' - elif not if_bool and then_bool and full_att_bool: + elif not if_bool and result_bool and full_att_bool: violates_rule = True # If Variants in 'if_dict' not found in deconstructed_DNA, but Variants in 'then_dict' are found in deconstructed_DNA, # and don't make up a full Attribute: - elif not if_bool and then_bool and not full_att_bool: - violates_rule = False + # elif not if_bool and result_bool and not full_att_bool: + # violates_rule = False - else: - violates_rule = False + return violates_rule, if_bool, result_bool, full_att_bool - return violates_rule, if_bool, then_bool, full_att_bool -# DONE -def create_dicts(hierarchy, rule_list_items): +def create_dicts(hierarchy, rule_list_items, result_dict_type): structure = { "attribute1": { "variant1": [ @@ -188,22 +229,6 @@ def create_dicts(hierarchy, rule_list_items): } } - def get_var_info(variant): - # Get info for variant dict - name = variant.split("_")[0] - order_number = variant.split("_")[1] - rarity_number = variant.split("_")[2] - attribute = "" - - for a in hierarchy: - for var in list(hierarchy[a].keys()): - if var == variant: - attribute = a - break - attribute_index = list(hierarchy.keys()).index(attribute) - - return [name, order_number, rarity_number, attribute, attribute_index] # list of Var info sent back - items_returned = collections.defaultdict(dict) for a in rule_list_items: for b in hierarchy: @@ -211,18 +236,19 @@ def create_dicts(hierarchy, rule_list_items): variant_list_of_a = list(hierarchy[a].keys()) variant_dict_of_a = {} for c in variant_list_of_a: - variant_dict_of_a[c] = get_var_info(c) + variant_dict_of_a[c] = get_var_info(c, hierarchy) items_returned[a] = variant_dict_of_a if a in list(hierarchy[b].keys()): # If 'a' is a Variant, add all info about that variant to items_returned - items_returned[b][a] = get_var_info(a) + items_returned[b][a] = get_var_info(a, hierarchy) + + items_returned = dict(items_returned) return dict(items_returned) def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMaterials): - deconstructed_DNA = singleDNA.split("-") didReconstruct = True originalDNA = str(singleDNA) @@ -231,18 +257,25 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMater didReconstruct = False for rule in logicFile: # Items from 'IF' key for a given rule - if_dict = create_dicts(hierarchy, logicFile[rule]["IF"]) + if_dict = create_dicts(hierarchy, logicFile[rule]["IF"], "IF") - # Items from 'THEN' key for a given rule - then_dict = create_dicts(hierarchy, logicFile[rule]["THEN"]) + result_dict_type = "" + if "THEN" in logicFile[rule]: + result_dict_type = "THEN" + if "NOT" in logicFile[rule]: + result_dict_type = "NOT" + + result_dict = create_dicts(hierarchy, logicFile[rule][result_dict_type], result_dict_type) + + # Change 'then_bool' to 'result_bool' violates_rule, if_bool, then_bool, full_att_bool = get_rule_break_type(hierarchy, deconstructed_DNA, - if_dict, then_dict) + if_dict, result_dict, result_dict_type) if violates_rule: - print(f"======={deconstructed_DNA} VIOLATES RULE======") + # print(f"======={deconstructed_DNA} VIOLATES RULE======") deconstructed_DNA = apply_rules_to_dna( - hierarchy, deconstructed_DNA, if_dict, then_dict, enableRarity + hierarchy, deconstructed_DNA, if_dict, result_dict, result_dict_type, enableRarity ) newDNA = reconstructDNA(deconstructed_DNA) diff --git a/main/Material_Generator.py b/main/Material_Generator.py index 9358aae..9da3c16 100644 --- a/main/Material_Generator.py +++ b/main/Material_Generator.py @@ -114,6 +114,15 @@ def apply_materials(hierarchy, singleDNA, materialsFile, enableRarity): if not complete: deconstructed_MaterialDNA[a] = "0" + # Make Attributes have the same materials: + # Order your Attributes alphabetically, then assign each Attribute a number, starting with 0. So Attribute 'A' = 0, + # Attribute 'B' = 1, 'C' = 2, 'D' = 3, etc. For each pair you want to equal another, add its number it to this list: + # synced_material_attributes = [1, 2] + # + # first_mat = deconstructed_MaterialDNA[synced_material_attributes[0]] + # for i in synced_material_attributes: + # deconstructed_MaterialDNA[i] = first_mat + material_DNA = "" for a in deconstructed_MaterialDNA: num = "-" + str(deconstructed_MaterialDNA[a])