diff --git a/__init__.py b/__init__.py index bd6d6e6..6e9ea3a 100644 --- a/__init__.py +++ b/__init__.py @@ -8,7 +8,6 @@ bl_info = { "category": "Development", } - # ======== Import handling ======== # import bpy @@ -34,15 +33,16 @@ from main import \ HeadlessUtil, \ loading_animation, \ Logic, \ + Material_Generator, \ Metadata, \ Rarity, \ Refactorer + from UILists import \ Custom_Metadata_UIList, \ Logic_UIList if "bpy" in locals(): - modules = { "Batch_Sorter": Batch_Sorter, "Checks": Checks, @@ -52,6 +52,7 @@ if "bpy" in locals(): "HeadlessUtil": HeadlessUtil, "loading_animation": loading_animation, "Logic": Logic, + "Material_Generator": Material_Generator, "Metadata": Metadata, "Rarity": Rarity, "Refactorer": Refactorer, @@ -69,6 +70,7 @@ if "bpy" in locals(): combinations: int = 0 recommended_limit: int = 0 + @persistent def Refresh_UI(dummy1, dummy2): """ @@ -79,7 +81,7 @@ def Refresh_UI(dummy1, dummy2): global recommended_limit combinations = (get_combinations.get_combinations()) - recommended_limit = int(round(combinations/2)) + recommended_limit = int(round(combinations / 2)) # Add panel classes that require refresh to this refresh_panels tuple: refresh_panel_classes = ( @@ -96,6 +98,7 @@ def Refresh_UI(dummy1, dummy2): redraw_panel(refresh_panel_classes) + bpy.app.handlers.depsgraph_update_post.append(Refresh_UI) @@ -115,10 +118,12 @@ def make_directories(save_path): os.makedirs(nftBatch_save_path) return Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path + def runAsHeadless(): """ For use when running from the command line. """ + def dumpSettings(settings): output = ( f"nftName={settings.nftName}\n" @@ -142,6 +147,8 @@ def runAsHeadless(): f"solana_description={settings.solana_description}\n" f"enableCustomFields={str(settings.enableCustomFields)}\n" f"customfieldsFile={settings.customfieldsFile}\n" + f"enableMaterials={str(settings.customfieldsFile)}\n" + f"materialsFile={settings.materialsFile}\n" ) print(output) @@ -179,6 +186,8 @@ def runAsHeadless(): settings.solanaDescription = pairs[18][1] settings.enableCustomFields = pairs[19][1] == 'True' settings.customfieldsFile = pairs[20][1] + settings.enableMaterials = pairs[21][1] == 'True' + settings.materialsFile = pairs[22][1] if args.save_path: settings.save_path = args.save_path @@ -199,10 +208,13 @@ def runAsHeadless(): enableRarity = settings.enableRarity enableLogic = settings.enableLogic + enableMaterials = settings.enableMaterials + materialsFile = settings.materialsFile + 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) + enableMaterials, materialsFile, Blend_My_NFTs_Output) Batch_Sorter.makeBatches(nftName, maxNFTs, nftsPerBatch, save_path, batch_json_save_path) elif args.operation == 'generate-nfts': @@ -255,7 +267,6 @@ def runAsHeadless(): # ======== User input Property Group ======== # class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): - # Create NFT Data Panel: nftName: bpy.props.StringProperty(name="NFT Name") @@ -264,11 +275,11 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): nftsPerBatch: bpy.props.IntProperty(name="NFTs Per Batch", default=1, min=1) # max=(combinations - offset) save_path: bpy.props.StringProperty( - name="Save Path", - description="Save path for NFT files", - default="", - maxlen=1024, - subtype="DIR_PATH" + name="Save Path", + description="Save path for NFT files", + default="", + maxlen=1024, + subtype="DIR_PATH" ) enableRarity: bpy.props.BoolProperty(name="Enable Rarity") @@ -276,18 +287,27 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): enableLogic: bpy.props.BoolProperty(name="Enable Logic") enable_Logic_Json: bpy.props.BoolProperty(name="Use Logic.json instead") logicFile: bpy.props.StringProperty( - name="Logic File Path", - description="Path where Logic.json is located.", - default="", - maxlen=1024, - subtype="FILE_PATH" + name="Logic File Path", + description="Path where Logic.json is located.", + default="", + maxlen=1024, + subtype="FILE_PATH" + ) + + enableMaterials: bpy.props.BoolProperty(name="Enable Materials") + materialsFile: bpy.props.StringProperty( + name="Materials File", + description="Path where Materials.json is located.", + default="", + maxlen=1024, + subtype="FILE_PATH" ) # Generate NFTs Panel: imageBool: bpy.props.BoolProperty(name="Image") imageEnum: bpy.props.EnumProperty( - name="Image File Format", - description="Select Image file format", + name="Image File Format", + description="Select Image file format", items=[ ('PNG', ".PNG", "Export NFT as PNG"), ('JPEG', ".JPEG", "Export NFT as JPEG") @@ -296,8 +316,8 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): animationBool: bpy.props.BoolProperty(name="Animation") animationEnum: bpy.props.EnumProperty( - name="Animation File Format", - description="Select Animation file format", + name="Animation File Format", + description="Select Animation file format", items=[ ('AVI_JPEG', '.avi (AVI_JPEG)', 'Export NFT as AVI_JPEG'), ('AVI_RAW', '.avi (AVI_RAW)', 'Export NFT as AVI_RAW'), @@ -308,21 +328,24 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): modelBool: bpy.props.BoolProperty(name="3D Model") modelEnum: bpy.props.EnumProperty( - name="3D Model File Format", - description="Select 3D Model file format", + name="3D Model File Format", + description="Select 3D Model file format", items=[ ('GLB', '.glb', 'Export NFT as .glb'), - ('GLTF_SEPARATE', '.gltf + .bin + textures', 'Export NFT as .gltf with separated textures in .bin + textures.'), + ('GLTF_SEPARATE', '.gltf + .bin + textures', + 'Export NFT as .gltf with separated textures in .bin + textures.'), ('GLTF_EMBEDDED', '.gltf', 'Export NFT as embedded .gltf file that contains textures.'), ('FBX', '.fbx', 'Export NFT as .fbx'), ('OBJ', '.obj', 'Export NFT as .obj'), ('X3D', '.x3d', 'Export NFT as .x3d'), ('STL', '.stl', 'Export NFT as .stl'), - ('VOX', '.vox (Experimental)', 'Export NFT as .vox, requires the voxwriter add on: https://github.com/Spyduck/voxwriter') + ('VOX', '.vox (Experimental)', + 'Export NFT as .vox, requires the voxwriter add on: https://github.com/Spyduck/voxwriter') ] ) - batchToGenerate: bpy.props.IntProperty(name="Batch To Generate", default=1, min=1) # max=(collectionSize / nftsPerBatch) + batchToGenerate: bpy.props.IntProperty(name="Batch To Generate", default=1, + min=1) # Refactor Batches & Create Metadata Panel: cardanoMetaDataBool: bpy.props.BoolProperty(name="Cardano Cip") @@ -336,11 +359,11 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup): enableCustomFields: bpy.props.BoolProperty(name="Enable Custom Metadata Fields") customfieldsFile: bpy.props.StringProperty( - name="Custom Fields File", - description="Path where Custom_Fields.json is located.", - default="", - maxlen=1024, - subtype="FILE_PATH" + name="Custom Fields File", + description="Path where Custom_Fields.json is located.", + default="", + maxlen=1024, + subtype="FILE_PATH" ) # Other Panel: @@ -361,19 +384,19 @@ class createData(bpy.types.Operator): name="Reverse Order") def execute(self, context): - nftName = bpy.context.scene.input_tool.nftName collectionSize = bpy.context.scene.input_tool.collectionSize nftsPerBatch = bpy.context.scene.input_tool.nftsPerBatch save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path) - logicFile = bpy.path.abspath(bpy.context.scene.input_tool.logicFile) # Logic_Dict will replace this, remove the open(logicFile) enableRarity = bpy.context.scene.input_tool.enableRarity + enableLogic = bpy.context.scene.input_tool.enableLogic enable_Logic_Json = bpy.context.scene.input_tool.enable_Logic_Json + logicFile = bpy.path.abspath(bpy.context.scene.input_tool.logicFile) - Logic_Dict = {} - + enableMaterials = bpy.context.scene.input_tool.enableMaterials + materialsFile = bpy.path.abspath(bpy.context.scene.input_tool.materialsFile) # Handling Custom Fields UIList input: if enableLogic: @@ -381,9 +404,10 @@ class createData(bpy.types.Operator): logicFile = json.load(open(logicFile)) Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) - DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, - logicFile, Blend_My_NFTs_Output) + DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, + enableMaterials, materialsFile, Blend_My_NFTs_Output) Batch_Sorter.makeBatches(nftName, collectionSize, nftsPerBatch, save_path, batch_json_save_path) + if enable_Logic_Json and not logicFile: self.report({'ERROR'}, f"No Logic.json file path set. Please set the file path to your Logic.json file.") @@ -405,9 +429,8 @@ class createData(bpy.types.Operator): } num += 1 Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) - DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, - enableLogic, - logicFile, Blend_My_NFTs_Output) + DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, + enableMaterials, materialsFile, Blend_My_NFTs_Output) Batch_Sorter.makeBatches(nftName, collectionSize, nftsPerBatch, save_path, batch_json_save_path) else: logicFile = {} @@ -423,19 +446,19 @@ class createData(bpy.types.Operator): } num += 1 Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) - DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, - enableLogic, - logicFile, Blend_My_NFTs_Output) + DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, + enableMaterials, materialsFile, Blend_My_NFTs_Output) Batch_Sorter.makeBatches(nftName, collectionSize, nftsPerBatch, save_path, batch_json_save_path) - + if not enableLogic: - Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(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) + Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) + DNA_Generator.send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, + enableMaterials, materialsFile, Blend_My_NFTs_Output) + Batch_Sorter.makeBatches(nftName, collectionSize, nftsPerBatch, save_path, batch_json_save_path) self.report({'INFO'}, f"NFT Data created!") return {"FINISHED"} + class exportNFTs(bpy.types.Operator): bl_idname = 'exporter.nfts' bl_label = 'Export NFTs' @@ -459,21 +482,27 @@ class exportNFTs(bpy.types.Operator): enableModelsBlender = bpy.context.scene.input_tool.modelBool modelFileFormat = bpy.context.scene.input_tool.modelEnum + enableMaterials = bpy.context.scene.input_tool.enableMaterials + materialsFile = bpy.path.abspath(bpy.context.scene.input_tool.materialsFile) + # fail state variables, set to no fail due to resume_failed_batch() Operator in BMNFTS_PT_GenerateNFTs Panel fail_state = False failed_batch = None failed_dna = None failed_dna_index = None - Exporter.render_and_save_NFTs(nftName, collectionSize, 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 + modelFileFormat, fail_state, failed_batch, failed_dna, failed_dna_index, + enableMaterials, materialsFile ) self.report({'INFO'}, f"All NFTs generated for batch {batchToGenerate}!") return {"FINISHED"} + class resume_failed_batch(bpy.types.Operator): bl_idname = 'exporter.resume_nfts' bl_label = 'Resume Failed Batch' @@ -500,7 +529,8 @@ 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, collectionSize, 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 ) @@ -509,6 +539,7 @@ class resume_failed_batch(bpy.types.Operator): return {"FINISHED"} + class refactor_Batches(bpy.types.Operator): """Refactor your collection? This action cannot be undone.""" bl_idname = 'refactor.batches' @@ -563,6 +594,7 @@ class refactor_Batches(bpy.types.Operator): def invoke(self, context, event): return context.window_manager.invoke_confirm(self, event) + class export_settings(bpy.types.Operator): """Export your settings into a configuration file.""" bl_idname = 'export.settings' @@ -621,6 +653,10 @@ class export_settings(bpy.types.Operator): "#Enable Custom Fields\n" f"enableCustomFields={str(settings.enableCustomFields)}\n" f"customfieldsFile={settings.customfieldsFile}\n" + "\n" + "#Enable Materials\n" + f"enableMaterials={str(settings.enableMaterials)}\n" + f"materialsFile={settings.materialsFile}\n" ) print(output, file=config) @@ -696,9 +732,17 @@ class BMNFTS_PT_CreateData(bpy.types.Panel): row = layout.row() row.prop(input_tool_scene, "logicFile") + row = layout.row() + row.prop(input_tool_scene, "enableMaterials") + + if bpy.context.scene.input_tool.enableMaterials: + row = layout.row() + row.prop(input_tool_scene, "materialsFile") + row = layout.row() self.layout.operator("create.data", icon='DISCLOSURE_TRI_RIGHT', text="Create Data") + class BMNFTS_PT_GenerateNFTs(bpy.types.Panel): bl_label = "Generate NFTs" bl_idname = "BMNFTS_PT_GenerateNFTs" @@ -751,6 +795,7 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel): row = layout.row() self.layout.operator("exporter.nfts", icon='RENDER_RESULT', text="Generate NFTs") + class BMNFTS_PT_Refactor(bpy.types.Panel): bl_label = "Refactor Batches & Create Metadata" bl_idname = "BMNFTS_PT_Refactor" @@ -825,6 +870,7 @@ class BMNFTS_PT_Refactor(bpy.types.Panel): row = layout.row() self.layout.operator("refactor.batches", icon='FOLDER_REDIRECT', text="Refactor Batches & Create Metadata") + class BMNFTS_PT_Other(bpy.types.Panel): bl_label = "Other" bl_idname = "BMNFTS_PT_Other" @@ -853,6 +899,11 @@ class BMNFTS_PT_Other(bpy.types.Panel): row = layout.row() layout.label(text=f"**Set a Save Path in Create NFT Data to Export Settings") + row = layout.row() + + row = layout.row() + layout.label(text=f"Looking for help?") + row = layout.row() row.operator("wm.url_open", text="Blend_My_NFTs Documentation", icon='URL').url = "https://github.com/torrinworx/Blend_My_NFTs" @@ -860,7 +911,6 @@ class BMNFTS_PT_Other(bpy.types.Panel): row = layout.row() row.operator("wm.url_open", text="YouTube Tutorials", icon='URL').url = "https://www.youtube.com/watch?v=ygKJYz4BjRs&list=PLuVvzaanutXcYtWmPVKu2bx83EYNxLRsX" - row = layout.row() row.operator("wm.url_open", text="Join Our Discord Community!", icon='URL').url = "https://discord.gg/UpZt5Un57t" @@ -885,6 +935,7 @@ classes = ( BMNFTS_PT_Other, ) + Custom_Metadata_UIList.classes_Custom_Metadata_UIList + Logic_UIList.classes_Logic_UIList + def register(): for cls in classes: bpy.utils.register_class(cls) @@ -897,7 +948,6 @@ def register(): bpy.types.Scene.logic_fields = CollectionProperty(type=Logic_UIList.CUSTOM_logic_objectCollection) bpy.types.Scene.logic_fields_index = IntProperty() - def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 7833841..b9c61e3 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -10,7 +10,7 @@ import json import random from functools import partial from .loading_animation import Loader -from . import Rarity, Logic, Checks +from . import Rarity, Logic, Checks, Material_Generator from .Constants import bcolors, removeList, remove_file_by_extension @@ -126,7 +126,7 @@ def get_hierarchy(): return hierarchy -def generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic): +def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile): """ Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList. """ @@ -172,8 +172,14 @@ def generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic): if enableLogic: singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile) - # print(f"Logic DNA: {singleDNA}") - # print("============\n") + print(f"Original DNA: {singleDNA}") + print("============\n") + + if enableMaterials: + singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile) + print(f"Materials DNA: {singleDNA}") + print("============\n") + return singleDNA def create_DNAList(): @@ -202,7 +208,8 @@ def generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic): return DataDictionary -def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output): +def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, enableMaterials, + materialsFile, 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 @@ -232,7 +239,8 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e def create_nft_data(): try: - DataDictionary = generateNFT_DNA(collectionSize, logicFile, enableRarity, enableLogic) + DataDictionary = generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, + materialsFile) NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") # Checks: diff --git a/main/Exporter.py b/main/Exporter.py index f638373..97507bc 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -18,7 +18,9 @@ def save_batch(batch, file_name): with open(os.path.join(file_name), 'w') as outfile: outfile.write(saved_batch + '\n') -def save_generation_state(batchToGenerate, batch_json_save_path, nftBatch_save_path, enableImages, imageFileFormat, enableAnimations, + +def save_generation_state(batchToGenerate, batch_json_save_path, nftBatch_save_path, enableImages, imageFileFormat, + enableAnimations, animationFileFormat, enableModelsBlender, modelFileFormat): """Saves date and time of generation start, and generation types; Images, Animations, 3D Models, and the file types for each.""" file_name = os.path.join(batch_json_save_path, "Batch{}.json".format(batchToGenerate)) @@ -51,14 +53,14 @@ def save_generation_state(batchToGenerate, batch_json_save_path, nftBatch_save_p save_batch(batch, file_name) -def save_completed(single_dna, a, x, batch_json_save_path, batchToGenerate): + +def save_completed(full_single_dna, a, x, batch_json_save_path, batchToGenerate): """Saves progress of rendering to batch.json file.""" file_name = os.path.join(batch_json_save_path, "Batch{}.json".format(batchToGenerate)) batch = json.load(open(file_name)) - index = batch["BatchDNAList"].index(a) - batch["BatchDNAList"][index][single_dna]["Complete"] = True + batch["BatchDNAList"][index][full_single_dna]["Complete"] = True batch["Generation Save"][-1]["DNA Generated"] = x save_batch(batch, file_name) @@ -79,17 +81,19 @@ def getBatchData(batchToGenerate, batch_json_save_path): return NFTs_in_Batch, hierarchy, BatchDNAList -def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path, nftBatch_save_path, enableImages, - imageFileFormat, enableAnimations, animationFileFormat, enableModelsBlender, - modelFileFormat, fail_state, failed_batch, failed_dna, failed_dna_index - ): + +def 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, + enableMaterials, materialsFile): """ 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. """ NFTs_in_Batch, hierarchy, BatchDNAList = getBatchData(batchToGenerate, batch_json_save_path) - + materialsFile = json.load(open(materialsFile)) time_start_1 = time.time() if fail_state: @@ -104,7 +108,9 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path x = 1 for a in BatchDNAList: - single_dna = list(a.keys())[0] + full_single_dna = list(a.keys())[0] + single_dna, material_dna = full_single_dna.split(':') + for i in hierarchy: for j in hierarchy[i]: bpy.data.collections[j].hide_render = True @@ -129,19 +135,67 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path dnaDictionary.update({x: k}) return dnaDictionary + def match_materialDNA_to_Material(single_dna, material_dna): + """ + Matches the Material DNA to it's selected Materials unless a 0 is present meaning no material for that variant was selected. + """ + listAttributes = list(hierarchy.keys()) + listDnaDecunstructed = single_dna.split('-') + listMaterialDNADeconstructed = material_dna.split('-') + + full_dna_dict = {} + + for attribute, variant, material in zip(listAttributes, listDnaDecunstructed, listMaterialDNADeconstructed): + + for var in hierarchy[attribute]: + if hierarchy[attribute][var]['number'] == variant: + variant = var + + if material != '0': + for variant_m in materialsFile: + if variant == variant_m: + for mat in materialsFile[variant_m]["Material List"]: + if mat.split('_')[1] == material: + material = mat + + full_dna_dict[variant] = material + + return full_dna_dict + dnaDictionary = match_DNA_to_Variant(single_dna) name = nftName + "_" + str(x) print(f"\n{bcolors.OK}|---Generating NFT {x}/{NFTs_in_Batch} ---|{bcolors.RESET}") print(f"DNA attribute list:\n{dnaDictionary}\nDNA Code:{single_dna}") + if enableMaterials: + materialdnaDictionary = match_materialDNA_to_Material(single_dna, material_dna) + + for var_mat in list(materialdnaDictionary.keys()): + if materialdnaDictionary[var_mat] != '0': + if not materialsFile[var_mat]['Variant Objects']: + """ + If objects to apply material to not specified, apply to all objects in Variant collection. + """ + + for obj in bpy.data.collections[var_mat].all_objects: + selected_object = bpy.data.objects.get(obj.name) + selected_object.active_material = bpy.data.materials[materialdnaDictionary[var_mat]] + + if materialsFile[var_mat]['Variant Objects']: + """ + If objects to apply material to are specified, apply material only to objects specified withing the Variant collection. + """ + for obj in materialsFile[var_mat]['Variant Objects']: + selected_object = bpy.data.objects.get(obj) + selected_object.active_material = bpy.data.materials[materialdnaDictionary[var_mat]] + for c in dnaDictionary: collection = dnaDictionary[c] if collection != '0': bpy.data.collections[collection].hide_render = False bpy.data.collections[collection].hide_viewport = False - time_start_2 = time.time() batchFolder = os.path.join(nftBatch_save_path, "Batch" + str(batchToGenerate)) @@ -292,7 +346,7 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path print(f"Completed {name} render in {time.time() - time_start_2}s") - save_completed(single_dna, a, x, batch_json_save_path, batchToGenerate) + save_completed(full_single_dna, a, x, batch_json_save_path, batchToGenerate) x += 1 diff --git a/main/Material_Generator.py b/main/Material_Generator.py new file mode 100644 index 0000000..cb02756 --- /dev/null +++ b/main/Material_Generator.py @@ -0,0 +1,100 @@ +# Purpose: +# The purpose of this file is to apply the materials a user sets in a given .json file to the Variant collection objects +# also specified in the .json file. The Materialized DNA is then returned in the following format: 1-1-1:1-1-1 +# Where the numbers right of the ":" are the material numbers applied to the respective Variants to the left of the ":" + +import bpy + +import json +import random + + +def select_material(materialList): + """Selects a material from a passed material list. """ + + number_List_Of_i = [] + rarity_List_Of_i = [] + ifZeroBool = None + + for material in materialList: + + material_order_num = material.split("_")[1] + number_List_Of_i.append(material_order_num) + + material_rarity_percent = material.split("_")[1] + 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 + + 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) + + return selected_material[0] + +def get_variant_att_index(variant, hierarchy): + variant_attribute = None + + for attribute in hierarchy: + for variant_h in hierarchy[attribute]: + if variant_h == variant: + variant_attribute = attribute + + attribute_index = list(hierarchy.keys()).index(variant_attribute) + variant_order_num = variant.split("_")[1] + return attribute_index, variant_order_num + +def match_DNA_to_Variant(hierarchy, singleDNA): + """ + Matches each DNA number separated by "-" to its attribute, then its variant. + """ + + listAttributes = list(hierarchy.keys()) + listDnaDecunstructed = singleDNA.split('-') + dnaDictionary = {} + + for i, j in zip(listAttributes, listDnaDecunstructed): + dnaDictionary[i] = j + + for x in dnaDictionary: + for k in hierarchy[x]: + kNum = hierarchy[x][k]["number"] + if kNum == dnaDictionary[x]: + dnaDictionary.update({x: k}) + return dnaDictionary + +def apply_materials(hierarchy, singleDNA, materialsFile): + """ + DNA with applied material example: "1-1:1-1" : + + The Material DNA will select the material for the Variant order number in the NFT DNA based on the Variant Material + list in the Variant_Material.json file. + """ + + singleDNADict = match_DNA_to_Variant(hierarchy, singleDNA) + materialsFile = json.load(open(materialsFile)) + deconstructed_MaterialDNA = {} + + for a in singleDNADict: + complete = False + for b in materialsFile: + if singleDNADict[a] == b: + mat = select_material(materialsFile[b]['Material List']) + deconstructed_MaterialDNA[a] = mat + complete = True + if not complete: + deconstructed_MaterialDNA[a] = "0" + + material_DNA = "" + for a in deconstructed_MaterialDNA: + num = "-" + str(deconstructed_MaterialDNA[a]) + material_DNA += num + material_DNA = ''.join(material_DNA.split('-', 1)) + + return f"{singleDNA}:{material_DNA}"