diff --git a/__init__.py b/__init__.py index 2f325d5..14c4432 100644 --- a/__init__.py +++ b/__init__.py @@ -157,9 +157,8 @@ def update_combinations(dummy1, dummy2): global recommended_limit global offset - combinations = (get_combinations.get_combinations_from_scene()) + combinations = (get_combinations.get_combinations()) recommended_limit = int(round(combinations/2)) - redraw_panel() @@ -176,7 +175,7 @@ class createData(bpy.types.Operator): def execute(self, context): nftName = bpy.context.scene.my_tool.nftName - maxNFTs = bpy.context.scene.my_tool.collectionSize + collectionSize = bpy.context.scene.my_tool.collectionSize nftsPerBatch = bpy.context.scene.my_tool.nftsPerBatch save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path) logicFile = bpy.path.abspath(bpy.context.scene.my_tool.logicFile) @@ -186,8 +185,8 @@ class createData(bpy.types.Operator): Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) - DNA_Generator.send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output) - Batch_Sorter.makeBatches(nftName, maxNFTs, nftsPerBatch, save_path, batch_json_save_path) + DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output) + Batch_Sorter.makeBatches(nftName, collectionSize, nftsPerBatch, save_path, batch_json_save_path) self.report({'INFO'}, f"NFT Data created!") @@ -203,7 +202,7 @@ class exportNFTs(bpy.types.Operator): nftName = bpy.context.scene.my_tool.nftName save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path) batchToGenerate = bpy.context.scene.my_tool.batchToGenerate - maxNFTs = bpy.context.scene.my_tool.collectionSize + collectionSize = bpy.context.scene.my_tool.collectionSize Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) @@ -222,7 +221,7 @@ class exportNFTs(bpy.types.Operator): failed_dna = None failed_dna_index = None - Exporter.render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path, nftBatch_save_path, enableImages, + Exporter.render_and_save_NFTs(nftName, collectionSize, batchToGenerate, batch_json_save_path, nftBatch_save_path, enableImages, imageFileFormat, enableAnimations, animationFileFormat, enableModelsBlender, modelFileFormat, fail_state, failed_batch, failed_dna, failed_dna_index ) @@ -241,7 +240,7 @@ class resume_failed_batch(bpy.types.Operator): nftName = bpy.context.scene.my_tool.nftName save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path) batchToGenerate = bpy.context.scene.my_tool.batchToGenerate - maxNFTs = bpy.context.scene.my_tool.collectionSize + collectionSize = bpy.context.scene.my_tool.collectionSize Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) fail_state, failed_batch, failed_dna, failed_dna_index = Checks.check_FailedBatches(batch_json_save_path) @@ -257,7 +256,7 @@ class resume_failed_batch(bpy.types.Operator): enableModelsBlender = batch["Generation Save"][-1]["Render_Settings"]["enableModelsBlender"] modelFileFormat = batch["Generation Save"][-1]["Render_Settings"]["modelFileFormat"] - Exporter.render_and_save_NFTs(nftName, maxNFTs, failed_batch, batch_json_save_path, nftBatch_save_path, enableImages, + Exporter.render_and_save_NFTs(nftName, collectionSize, failed_batch, batch_json_save_path, nftBatch_save_path, enableImages, imageFileFormat, enableAnimations, animationFileFormat, enableModelsBlender, modelFileFormat, fail_state, failed_batch, failed_dna, failed_dna_index ) @@ -380,8 +379,12 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel): row.prop(mytool, "batchToGenerate") save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path) - Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) + Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data") + batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data") + nftBatch_save_path = os.path.join(save_path, "Blend_My_NFTs Output", "Generated NFT Batches") + fail_state, failed_batch, failed_dna, failed_dna_index = Checks.check_FailedBatches(batch_json_save_path) + if fail_state: row = layout.row() self.layout.operator("exporter.nfts", icon='RENDER_RESULT', text="Generate NFTs") diff --git a/main/Checks.py b/main/Checks.py index 05b7aca..4c258cc 100644 --- a/main/Checks.py +++ b/main/Checks.py @@ -11,6 +11,7 @@ import os import json from collections import Counter, defaultdict +from . import DNA_Generator, get_combinations class bcolors: """ @@ -22,6 +23,7 @@ class bcolors: ERROR = '\033[91m' # RED RESET = '\033[0m' # RESET COLOR + # Checks: def check_Scene(): """ @@ -29,11 +31,36 @@ def check_Scene(): violations. """ + script_ignore_exists = None # True if Script_Ignore collection exists in Blender scene + attribute_naming_conventions = None # True if all attributes in Blender scene follow BMNFTs naming conventions + variant_naming_conventions = None # True if all variants in Blender scene follow BMNFTs naming conventions + object_placing_conventions = None # True if all objects are within either Script_Ignore or a variant collection + + hierarchy = DNA_Generator.get_hierarchy() + + # script_ignore_exists: + try: + scriptIgnoreCollection = bpy.data.collections["Script_Ignore"] + except KeyError: + raise TypeError( + f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" + f"collection to your Blender scene and ensure the name is exactly 'Script_Ignore'. For more information, " + f"see:" + f"\nhttps://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n{bcolors.RESET}" + ) + else: + script_ignore_exists = True + + collections = bpy.context.scene.collection + print(collections) + + # attribute_naming_conventions + + def check_Rarity(hierarchy, DNAList, save_path): """Checks rarity percentage of each Variant, then sends it to RarityData.json in NFT_Data folder.""" numNFTsGenerated = len(DNAList) - attributeNames = [] numDict = defaultdict(list) hierarchy.keys() @@ -74,9 +101,11 @@ def check_Rarity(hierarchy, DNAList, save_path): completeData[i] = x - print(completeData) - - print(bcolors.OK + "Rarity Checker is active. These are the percentages for each variant per attribute you set in your .blend file:" + bcolors.RESET) + print( + f"\n{bcolors.OK}\n" + f"Rarity Checker is active. These are the percentages for each variant per attribute you set in your .blend file:" + f"\n{bcolors.RESET}" + ) for i in completeData: print(i + ":") @@ -105,42 +134,27 @@ def check_Duplicates(DNAList): print(f"NFTRecord.json contains {duplicates} duplicate NFT DNA.") def check_FailedBatches(batch_json_save_path): - batch_folders = os.listdir(batch_json_save_path) fail_state = False failed_batch = None failed_dna = None failed_dna_index = None - for i in batch_folders: - batch = json.load(open(os.path.join(batch_json_save_path, i))) - NFTs_in_Batch = batch["NFTs_in_Batch"] - if "Generation Save" in batch: - dna_generated = batch["Generation Save"][-1]["DNA Generated"] - if dna_generated < NFTs_in_Batch: - fail_state = True - failed_batch = int(i.removeprefix("Batch").removesuffix(".json")) - failed_dna = dna_generated + if os.path.isdir(batch_json_save_path): + batch_folders = os.listdir(batch_json_save_path) + for i in batch_folders: + batch = json.load(open(os.path.join(batch_json_save_path, i))) + NFTs_in_Batch = batch["NFTs_in_Batch"] + if "Generation Save" in batch: + dna_generated = batch["Generation Save"][-1]["DNA Generated"] + if dna_generated < NFTs_in_Batch: + fail_state = True + failed_batch = int(i.removeprefix("Batch").removesuffix(".json")) + failed_dna = dna_generated return fail_state, failed_batch, failed_dna, failed_dna_index - # Raise Errors: -def raise_Error_ScriptIgnore(): - """Checks if Script_Ignore collection exists, if not raises error.""" - - try: - scriptIgnore = bpy.data.collections["Script_Ignore"] - return scriptIgnore - except KeyError: - raise KeyError( - f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"Script_Ignore collection not found in Blender scene. Please add the Script_Ignore " - f"collection to Blender scene or ensure the spelling is exactly 'Script_Ignore'. For more information, " - f"see:\n{bcolors.RESET}" - f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n{bcolors.RESET}" - ) - def raise_Error_numBatches(maxNFTs, nftsPerBatch): """Checks if number of Batches is less than maxNFTs, if not raises error.""" @@ -148,15 +162,18 @@ def raise_Error_numBatches(maxNFTs, nftsPerBatch): numBatches = maxNFTs / nftsPerBatch return numBatches except ZeroDivisionError: - print(f"{bcolors.ERROR} ERROR:\nnftsPerBatch in config.py needs to be a positive integer. {bcolors.RESET}") raise ZeroDivisionError( f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" - f"The number of combinations is less than the number of \n{bcolors.RESET}" + f"The number of NFTs per Batch must be greater than ZERO." + f"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{bcolors.RESET}" ) -def raise_Error_ZeroCombinations(combinations): +def raise_Error_ZeroCombinations(): """Checks if combinations is greater than 0, if so, raises error.""" - if combinations == 0: + if get_combinations.get_combinations() == 0: raise ValueError( f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" f"The number of all possible combinations is ZERO. Please review your Blender scene and ensure it follows " @@ -177,13 +194,29 @@ def raise_Error_numBatchesGreaterThan(numBatches): # Raise Warnings: -def raise_Warning_maxNFTs(nftsPerBatch, maxNFTs): +def raise_Warning_maxNFTs(nftsPerBatch, collectionSize): + """ + Prints warning if nftsPerBatch is greater than collectionSize. """ - """ - - if nftsPerBatch > maxNFTs: - raise ( + if nftsPerBatch > collectionSize: + raise ValueError( f"\n{bcolors.WARNING}Blend_My_NFTs Warning:\n" f"The number of NFTs Per Batch you set is smaller than the NFT Collection Size you set.\n{bcolors.RESET}" ) + +def raise_Warning_collectionSize(DNAList, collectionSize): + """ + Prints warning if BMNFTs cannot generate requested number of NFTs from a given collectionSize. + """ + + if len(DNAList) < collectionSize: + print(f"\n{bcolors.WARNING} \nWARNING: \n" + f"Blend_My_NFTs cannot generate {collectionSize} NFTs." + f" Only {len(DNAList)} NFT DNA were generated." + + f"\nThis might be for a number of reasons:" + f"\n a) Rarity is preventing combinations from being generated (See https://github.com/torrinworx/Blend_My_NFTs#notes-on-rarity-and-weighted-variants).\n" + f"\n b) Logic is preventing combinations from being generated (See https://github.com/torrinworx/Blend_My_NFTs#logic).\n" + f"\n c) The number of possible combinations of your NFT collection is too low. Add more Variants or Attributes to increase the recommended collection size.\n" + f"\n{bcolors.RESET}") diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 38f0246..d48673d 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -23,15 +23,14 @@ class bcolors: RESET = '\033[0m' # RESET COLOR -def returnData(maxNFTs, nftsPerBatch): +def get_hierarchy(): """ - Generates important variables, dictionaries, and lists needed to be stored to catalog the NFTs. - :return: listAllCollections, attributeCollections, attributeCollections1, hierarchy, variantMetaData, possibleCombinations + Returns the hierarchy of a given Blender scene. """ coll = bpy.context.scene.collection - scriptIgnore = Checks.raise_Error_ScriptIgnore() + scriptIgnoreCollection = bpy.data.collections["Script_Ignore"] listAllCollInScene = [] listAllCollections = [] @@ -47,7 +46,7 @@ def returnData(maxNFTs, nftsPerBatch): for i in listAllCollInScene: listAllCollections.append(i.name) - listAllCollections.remove(scriptIgnore.name) + listAllCollections.remove(scriptIgnoreCollection.name) if "Scene Collection" in listAllCollections: listAllCollections.remove("Scene Collection") @@ -55,20 +54,19 @@ def returnData(maxNFTs, nftsPerBatch): if "Master Collection" in listAllCollections: listAllCollections.remove("Master Collection") - def allScriptIgnore(scriptIgnore): - """ - Removes all collections, sub collections in Script_Ignore collection from listAllCollections. - """ - for coll in list(scriptIgnore.children): + 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) - allScriptIgnore(scriptIgnore) + allScriptIgnore(scriptIgnoreCollection) listAllCollections.sort() - exclude = ["_", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + exclude = ["_"] # Excluding characters that identify a Variant attributeCollections = copy.deepcopy(listAllCollections) def filter_num(): @@ -91,8 +89,6 @@ def returnData(maxNFTs, nftsPerBatch): Creates a dictionary of each attribute """ allAttDataList = {} - count = 0 - previousAttribute = "" for i in attributeVariants: def getName(i): @@ -123,67 +119,28 @@ def returnData(maxNFTs, nftsPerBatch): variantMetaData = attributeData(attributeVariants) - def getHierarchy(): - """ - Constructs the hierarchy dictionary from attributeCollections1 and variantMetaData. - """ - 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 + 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 - for a in hierarchy: - for b in hierarchy[a]: - for x in variantMetaData: - if str(x) == str(b): - (hierarchy[a])[b] = variantMetaData[x] + 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 - hierarchy = getHierarchy() - - def numOfCombinations(hierarchy): - """ - Returns "combinations" the number of all possible NFT combinations. - """ - - hierarchyByNum = [] - - for i in hierarchy: - # Ignore Collections with nothing in them - if len(hierarchy[i]) != 0: - hierarchyByNum.append(len(hierarchy[i])) - else: - print(f"The following collection has been identified as empty: {i}") - - combinations = 1 - for i in hierarchyByNum: - combinations = combinations*i - - # Checks: - numBatches = Checks.raise_Error_numBatches(maxNFTs, nftsPerBatch) - - Checks.raise_Error_ZeroCombinations(combinations) - - Checks.raise_Error_numBatchesGreaterThan(numBatches) - - Checks.raise_Error_numBatchesGreaterThan(numBatches) - - return combinations - - possibleCombinations = numOfCombinations(hierarchy) - - return listAllCollections, attributeCollections, attributeCollections1, hierarchy, possibleCombinations - -def generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic): +def generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic): """ Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList. """ - listAllCollections, attributeCollections, attributeCollections1, hierarchy, possibleCombinations = returnData(maxNFTs, nftsPerBatch) + hierarchy = get_hierarchy() # DNA random, Rarity and Logic methods: DataDictionary = {} @@ -232,10 +189,10 @@ def generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic) """Creates DNAList. Loops through createDNARandom() and applies Rarity, and Logic while checking if all DNA are unique""" DNASetReturn = set() - for i in range(maxNFTs): + for i in range(collectionSize): dnaPushToList = partial(singleCompleteDNA) - DNASetReturn |= {''.join([dnaPushToList()]) for _ in range(maxNFTs - len(DNASetReturn))} + DNASetReturn |= {''.join([dnaPushToList()]) for _ in range(collectionSize - len(DNASetReturn))} DNAListReturn = list(DNASetReturn) @@ -244,21 +201,17 @@ def generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic) DNAList = create_DNAList() # Messages: - if len(DNAList) < maxNFTs: - print(f"{bcolors.ERROR} \nWARNING: \n" - f"You are seeing this warning because the program cannot generate {maxNFTs} NFTs with rarity enabled. " - f"Only {len(DNAList)} NFT DNA were generated." - f"Either A) Lower the number of NFTs you wish to create, or B) Increase the maximum number of possible NFTs by" - f" creating more variants and attributes in your .blend file.{bcolors.RESET}") + + Checks.raise_Warning_collectionSize(DNAList, collectionSize) # Data stored in batchDataDictionary: DataDictionary["numNFTsGenerated"] = len(DNAList) DataDictionary["hierarchy"] = hierarchy DataDictionary["DNAList"] = DNAList - return DataDictionary, possibleCombinations + return DataDictionary -def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output): +def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output): """ 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 @@ -266,10 +219,13 @@ def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLo repeate DNA. """ + # Checking Scene is compatible with BMNFTs: + Checks.check_Scene() + # Messages: print( f"\n========================================\n" - f"Creating NFT Data. Generating {maxNFTs} NFT DNA.\n" + f"Creating NFT Data. Generating {collectionSize} NFT DNA.\n" ) if not enableRarity and not enableLogic: @@ -282,17 +238,21 @@ def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLo print(f"{bcolors.OK}Logic is ON. Rules listed in {logicFile} will be taken into account.\n{bcolors.RESET}") time_start = time.time() + def create_nft_data(): try: - DataDictionary, possibleCombinations = generateNFT_DNA(maxNFTs, nftsPerBatch, logicFile, enableRarity, enableLogic) + DataDictionary = generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic) NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") # Checks: - Checks.raise_Warning_maxNFTs(nftsPerBatch, maxNFTs) - - Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"], os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data")) + 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")) + except FileNotFoundError: raise FileNotFoundError( f"\n{bcolors.ERROR}Blend_My_NFTs Error:\n" @@ -301,8 +261,6 @@ def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLo f"see:\n{bcolors.RESET}" f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n" ) - else: - print("Something unexpected happened. Please check Blender System Console Traceback error for more information.") finally: loading.stop() @@ -324,8 +282,6 @@ def send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLo 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() diff --git a/main/Logic.py b/main/Logic.py index 46f2123..4d3a6d8 100644 --- a/main/Logic.py +++ b/main/Logic.py @@ -99,10 +99,21 @@ def reconstructDNA(deconstructedDNA): reconstructed_DNA += num return (''.join(reconstructed_DNA.split('-', 1))) +def strip_empty_variant(num_list): + for i in num_list: + var_list = num_list[i] + var_list.remove("0") + num_list[i] = var_list + return num_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 + + 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 \ @@ -116,6 +127,7 @@ def never_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): 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 \ @@ -130,6 +142,7 @@ def only_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2): 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 @@ -147,7 +160,6 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile): didReconstruct = True originalDNA = str(singleDNA) - resultDNA = str(singleDNA) while didReconstruct: didReconstruct = False @@ -168,6 +180,12 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile): 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: @@ -177,15 +195,20 @@ def logicafyDNAsingle(hierarchy, singleDNA, logicFile): 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 - resultDNA = str(newDNA) - break + newDNA = reconstructDNA(deconstructed_DNA) + if newDNA != originalDNA: + originalDNA = str(newDNA) + didReconstruct = True + break - return resultDNA + return str(reconstructDNA(deconstructed_DNA)) \ No newline at end of file diff --git a/main/get_combinations.py b/main/get_combinations.py index 9b7ff7f..e3aff49 100644 --- a/main/get_combinations.py +++ b/main/get_combinations.py @@ -1,6 +1,6 @@ import bpy -import re -import copy + +from . import DNA_Generator class bcolors: """ @@ -11,169 +11,25 @@ class bcolors: ERROR = '\033[91m' # RED RESET = '\033[0m' # RESET COLOR -def stripColorFromName(name): - return "_".join(name.split("_")[:-1]) -def get_combinations_from_scene(): +def get_combinations(): """ - Generates important variables, dictionaries, and lists needed to be stored to catalog the NFTs. - :return: listAllCollections, attributeCollections, attributeCollections1, hierarchy, variantMetaData, possibleCombinations + Returns "combinations", the number of all possible NFT DNA for a given Blender scene formatted to BMNFTs conventions + combinations. """ - coll = bpy.context.scene.collection + hierarchy = DNA_Generator.get_hierarchy() + hierarchyByNum = [] - try: - scriptIgnore = bpy.data.collections["Script_Ignore"] - except: - print(f"{bcolors.ERROR} ERROR:\nScript_Ignore collection is not in .blend file scene. Please add the Script_Ignore collection to your " - f".blend file scene. For more information, read the README.md file.\n {bcolors.RESET}") + for i in hierarchy: + # Ignore Collections with nothing in them + if len(hierarchy[i]) != 0: + hierarchyByNum.append(len(hierarchy[i])) + else: + print(f"The following collection has been identified as empty: {i}") - listAllCollInScene = [] - listAllCollections = [] + combinations = 1 + for i in hierarchyByNum: + combinations = combinations*i - 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 i in listAllCollInScene: - listAllCollections.append(i.name) - - listAllCollections.remove(scriptIgnore.name) - - if "Scene Collection" in listAllCollections: - listAllCollections.remove("Scene Collection") - - if "Master Collection" in listAllCollections: - listAllCollections.remove("Master Collection") - - def allScriptIgnore(collection): - ''' - Removes all collections, sub collections in Script_Ignore collection from listAllCollections. - ''' - for coll in list(collection.children): - listAllCollections.remove(coll.name) - listColl = list(coll.children) - if len(listColl) > 0: - allScriptIgnore(coll) - - allScriptIgnore(scriptIgnore) - listAllCollections.sort() - - exclude = ["_", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] - 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) - - for i in range(len(listAllCollections)): - filter_num() - - attributeVariants = [x for x in listAllCollections if x not in attributeCollections] - attributeCollections1 = copy.deepcopy(attributeCollections) - - def attributeData(attributeVariants): - """ - Creates a dictionary of each attribute - """ - allAttDataList = {} - count = 0 - previousAttribute = "" - for i in attributeVariants: - - def getName(i): - """ - Returns the name of "i" attribute variant - """ - name = i.split("_")[0] - return name - - def getOrder_rarity(i): - """ - Returns the "order", "rarity" and "color" (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) - - name = getName(i) - orderRarity = getOrder_rarity(i) - - if len(orderRarity) == 0: - print(f"{bcolors.ERROR} \nERROR: {bcolors.RESET}") - print(f"The collection {i} doesn't follow the naming conventions of attributes. Please move this \n" - "colleciton to Script_Ignore or review proper collection format in README.md") - return - - elif len(orderRarity) > 0: - number = orderRarity[0] - rarity = orderRarity[1] - - eachObject = {"name": name, "number": number, "rarity": rarity} - allAttDataList[i] = eachObject - return allAttDataList - - variantMetaData = attributeData(attributeVariants) - - def getHierarchy(): - """ - Constructs the hierarchy dictionary from attributeCollections1 and variantMetaData. - """ - 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 - - 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 - - hierarchy = getHierarchy() - - def numOfCombinations(hierarchy): - """ - Returns "combinations" the number of all possible NFT combinations. - """ - - hierarchyByNum = [] - - for i in hierarchy: - # Ignore Collections with nothing in them - if len(hierarchy[i]) != 0: - hierarchyByNum.append(len(hierarchy[i])) - else: - print(f"The following collection has been identified as empty: {i}") - - combinations = 1 - for i in hierarchyByNum: - combinations = combinations*i - - if combinations == 0: - print(bcolors.ERROR + "\nERROR:" + bcolors.RESET) - print("The number of all possible combinations is equal to 0. Please review your collection hierarchy" - "and ensure it is formatted correctly. Please review README.md for more information. \nHere is the " - "hierarchy of all collections the DNA_Generator gathered from your .blend file, excluding those in " - f"Script_Ignore: {hierarchy}") - - return combinations - - possibleCombinations = numOfCombinations(hierarchy) - - return possibleCombinations + return combinations