Fixing merge issues

Minor bug fixes to exporter and cleaning up code.
pull/136/head
Torrin Leonard 2022-08-11 09:48:01 -04:00
rodzic 752fa4b6e9
commit 4f42106fa3
9 zmienionych plików z 1035 dodań i 737 usunięć

Wyświetl plik

@ -117,10 +117,8 @@ class CUSTOM_logic_objectCollection(PropertyGroup):
name="Rule Type",
description="Select the Rule Type",
items=[
('Never With', "Never With", ""),
('Only With', "Only With", ""),
('Always With', "Always With", ""),
('THEN', "Then", ""),
('NOT', "Not", ""),
]
)
item_list2: StringProperty(default="Item List 2")

Wyświetl plik

@ -1,15 +1,18 @@
bl_info = {
"name": "Blend_My_NFTs",
"author": "Torrin Leonard, This Cozy Studio Inc",
"version": (4, 0, 2),
"blender": (3, 2, 0),
"version": (4, 5, 0),
"blender": (3, 2, 2),
"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.",
"support": "COMMUNITY",
"doc_url": "https://github.com/torrinworx/Blend_My_NFTs",
"tracker_url": "https://github.com/torrinworx/Blend_My_NFTs/issues/new",
"category": "Development",
}
BMNFTS_VERSION = "v4.0.2"
LAST_UPDATED = "8:19AM, May 31st, 2022"
BMNFTS_VERSION = "v4.5.0"
LAST_UPDATED = "8:19AM, Aug 11th, 2022"
# ======== Import handling ======== #
@ -18,17 +21,20 @@ from bpy.app.handlers import persistent
from bpy.props import (IntProperty,
BoolProperty,
CollectionProperty)
# Python modules:
import os
import sys
import json
import importlib
from dataclasses import dataclass
import traceback
from typing import Any
from dataclasses import dataclass
from datetime import datetime, timezone
# "a little hacky bs" - matt159 ;)
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
# Local file imports:
from main import \
Checks, \
DNA_Generator, \
@ -74,7 +80,41 @@ 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
def Refresh_UI(dummy1, dummy2):
"""
Refreshes the UI upon user interacting with Blender (using depsgraph_update_post handler). Might be a better handler
to use.
"""
global combinations
global recommended_limit
combinations = (get_combinations.get_combinations())
recommended_limit = int(round(combinations / 2))
# Add panel classes that require refresh to this refresh_panels tuple:
refresh_panel_classes = (
BMNFTS_PT_CreateData,
)
def redraw_panel(panels):
for i in panels:
try:
bpy.utils.unregister_class(i)
except Exception:
print(traceback.format_exc())
bpy.utils.register_class(i)
redraw_panel(refresh_panel_classes)
bpy.app.handlers.depsgraph_update_post.append(Refresh_UI)
# ======== Defining BMNFTs Data ======== #
@dataclass
class BMNFTData:
nftName: str
@ -115,6 +155,17 @@ class BMNFTData:
enableRarity: bool
enableAutoShutdown: bool
specify_timeBool: bool
hours: int
minutes: int
emailNotificationBool: bool
sender_from: str
email_password: str
receiver_to: str
custom_Fields: dict = None
fail_state: Any = False
failed_batch: Any = None
@ -124,82 +175,64 @@ class BMNFTData:
def __post_init__(self):
self.custom_Fields = {}
def getBMNFTData():
_save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
_Blend_My_NFTs_Output, _batch_json_save_path, _nftBatch_save_path = make_directories(_save_path)
data = BMNFTData (
nftName = bpy.context.scene.input_tool.nftName,
save_path = _save_path,
nftsPerBatch = bpy.context.scene.input_tool.nftsPerBatch,
batchToGenerate = bpy.context.scene.input_tool.batchToGenerate,
collectionSize = bpy.context.scene.input_tool.collectionSize,
data = BMNFTData(
nftName=bpy.context.scene.input_tool.nftName,
save_path=_save_path,
nftsPerBatch=bpy.context.scene.input_tool.nftsPerBatch,
batchToGenerate=bpy.context.scene.input_tool.batchToGenerate,
collectionSize=bpy.context.scene.input_tool.collectionSize,
enableRarity = bpy.context.scene.input_tool.enableRarity,
enableRarity=bpy.context.scene.input_tool.enableRarity,
Blend_My_NFTs_Output = _Blend_My_NFTs_Output,
batch_json_save_path = _batch_json_save_path,
nftBatch_save_path = _nftBatch_save_path,
Blend_My_NFTs_Output=_Blend_My_NFTs_Output,
batch_json_save_path=_batch_json_save_path,
nftBatch_save_path=_nftBatch_save_path,
enableLogic = bpy.context.scene.input_tool.enableLogic,
enable_Logic_Json = bpy.context.scene.input_tool.enable_Logic_Json,
logicFile = bpy.context.scene.input_tool.logicFile,
enableLogic=bpy.context.scene.input_tool.enableLogic,
enable_Logic_Json=bpy.context.scene.input_tool.enable_Logic_Json,
logicFile=bpy.context.scene.input_tool.logicFile,
enableImages = bpy.context.scene.input_tool.imageBool,
imageFileFormat = bpy.context.scene.input_tool.imageEnum,
enableImages=bpy.context.scene.input_tool.imageBool,
imageFileFormat=bpy.context.scene.input_tool.imageEnum,
enableAnimations = bpy.context.scene.input_tool.animationBool,
animationFileFormat = bpy.context.scene.input_tool.animationEnum,
enableAnimations=bpy.context.scene.input_tool.animationBool,
animationFileFormat=bpy.context.scene.input_tool.animationEnum,
enableModelsBlender = bpy.context.scene.input_tool.modelBool,
modelFileFormat = bpy.context.scene.input_tool.modelEnum,
enableModelsBlender=bpy.context.scene.input_tool.modelBool,
modelFileFormat=bpy.context.scene.input_tool.modelEnum,
enableCustomFields = bpy.context.scene.input_tool.enableCustomFields,
enableCustomFields=bpy.context.scene.input_tool.enableCustomFields,
cardanoMetaDataBool = bpy.context.scene.input_tool.cardanoMetaDataBool,
solanaMetaDataBool = bpy.context.scene.input_tool.solanaMetaDataBool,
erc721MetaData = bpy.context.scene.input_tool.erc721MetaData,
cardanoMetaDataBool=bpy.context.scene.input_tool.cardanoMetaDataBool,
solanaMetaDataBool=bpy.context.scene.input_tool.solanaMetaDataBool,
erc721MetaData=bpy.context.scene.input_tool.erc721MetaData,
cardano_description = bpy.context.scene.input_tool.cardano_description,
solana_description = bpy.context.scene.input_tool.solana_description,
erc721_description = bpy.context.scene.input_tool.erc721_description,
cardano_description=bpy.context.scene.input_tool.cardano_description,
solana_description=bpy.context.scene.input_tool.solana_description,
erc721_description=bpy.context.scene.input_tool.erc721_description,
enableMaterials = bpy.context.scene.input_tool.enableMaterials,
materialsFile = bpy.path.abspath(bpy.context.scene.input_tool.materialsFile)
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,
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,
)
return data
@persistent
def Refresh_UI(dummy1, dummy2):
"""
Refreshes the UI upon user interacting with Blender (using depsgraph_update_post handler). Might be a better handler
to use.
"""
global combinations
global recommended_limit
combinations = (get_combinations.get_combinations())
recommended_limit = int(round(combinations / 2))
# Add panel classes that require refresh to this refresh_panels tuple:
refresh_panel_classes = (
BMNFTS_PT_CreateData,
)
def redraw_panel(refresh_panel_classes):
for i in refresh_panel_classes:
try:
bpy.utils.unregister_class(i)
except:
pass
bpy.utils.register_class(i)
redraw_panel(refresh_panel_classes)
bpy.app.handlers.depsgraph_update_post.append(Refresh_UI)
# ======== Helper functions ======== #
def make_directories(save_path):
@ -236,29 +269,29 @@ def runAsHeadless():
def dumpSettings(settings):
output = (
f"nftName={ settings.nftName }\n"
f"collectionSize={ str(settings.collectionSize) }\n"
f"nftsPerBatch={ str(settings.nftsPerBatch) }\n"
f"save_path={ settings.save_path }\n"
f"enableRarity={ (settings.enableRarity) }\n"
f"enableLogic={ str(settings.enableLogic) }\n"
f"imageBool={ str(settings.imageBool) }\n"
f"imageEnum={ settings.imageEnum }\n"
f"animationBool={ str(settings.animationBool) }\n"
f"animationEnum={ settings.animationEnum }\n"
f"modelBool={ str(settings.modelBool) }\n"
f"modelEnum={ settings.modelEnum }\n"
f"batchToGenerate={ str(settings.batchToGenerate) }\n"
f"cardanoMetaDataBool={ str(settings.cardanoMetaDataBool) }\n"
f"cardano_description={ settings.cardano_description }\n"
f"erc721MetaData={ str(settings.erc721MetaData) }\n"
f"erc721_description={ settings.erc721_description }\n"
f"solanaMetaDataBool={ str(settings.solanaMetaDataBool) }\n"
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"
f"nftName={settings.nftName}\n"
f"collectionSize={str(settings.collectionSize)}\n"
f"nftsPerBatch={str(settings.nftsPerBatch)}\n"
f"save_path={settings.save_path}\n"
f"enableRarity={(settings.enableRarity)}\n"
f"enableLogic={str(settings.enableLogic)}\n"
f"imageBool={str(settings.imageBool)}\n"
f"imageEnum={settings.imageEnum}\n"
f"animationBool={str(settings.animationBool)}\n"
f"animationEnum={settings.animationEnum}\n"
f"modelBool={str(settings.modelBool)}\n"
f"modelEnum={settings.modelEnum}\n"
f"batchToGenerate={str(settings.batchToGenerate)}\n"
f"cardanoMetaDataBool={str(settings.cardanoMetaDataBool)}\n"
f"cardano_description={settings.cardano_description}\n"
f"erc721MetaData={str(settings.erc721MetaData)}\n"
f"erc721_description={settings.erc721_description}\n"
f"solanaMetaDataBool={str(settings.solanaMetaDataBool)}\n"
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)
@ -425,10 +458,30 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup):
subtype="FILE_PATH"
)
# TODO: Add 'Other' panel inputs to Headless functionality.
# Other Panel:
enableAutoSave: bpy.props.BoolProperty(name="Auto Save Before Generation",
description="Automatically saves your Blender file when 'Generate NFTs & Create Metadata' button is clicked")
# Auto Shutdown:
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)
# 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:
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD')
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') # Test code for future faetures
# ======== Main Operators ======== #
@ -448,7 +501,8 @@ class createData(bpy.types.Operator):
if input.enableLogic:
if input.enable_Logic_Json and not input.logicFile:
self.report({'ERROR'}, f"No Logic.json file path set. Please set the file path to your Logic.json file.")
self.report({'ERROR'},
f"No Logic.json file path set. Please set the file path to your Logic.json file.")
Intermediate.send_To_Record_JSON(input)
@ -470,7 +524,6 @@ class exportNFTs(bpy.types.Operator):
name="Reverse Order")
def execute(self, context):
input = getBMNFTData()
# Handling Custom Fields UIList input:
@ -498,42 +551,42 @@ class resume_failed_batch(bpy.types.Operator):
_fail_state, _failed_batch, _failed_dna, _failed_dna_index = Checks.check_FailedBatches(_batch_json_save_path)
input = BMNFTData (
nftName = batchData["Generation Save"][-1]["Render_Settings"]["nftName"],
save_path = _save_path,
collectionSize = batchData["Generation Save"][-1]["Render_Settings"]["collectionSize"],
input = BMNFTData(
nftName=batchData["Generation Save"][-1]["Render_Settings"]["nftName"],
save_path=_save_path,
collectionSize=batchData["Generation Save"][-1]["Render_Settings"]["collectionSize"],
Blend_My_NFTs_Output = _Blend_My_NFTs_Output,
batch_json_save_path = _batch_json_save_path,
nftBatch_save_path = batchData["Generation Save"][-1]["Render_Settings"]["nftBatch_save_path"],
Blend_My_NFTs_Output=_Blend_My_NFTs_Output,
batch_json_save_path=_batch_json_save_path,
nftBatch_save_path=batchData["Generation Save"][-1]["Render_Settings"]["nftBatch_save_path"],
enableImages = batchData["Generation Save"][-1]["Render_Settings"]["enableImages"],
imageFileFormat = batchData["Generation Save"][-1]["Render_Settings"]["imageFileFormat"],
enableImages=batchData["Generation Save"][-1]["Render_Settings"]["enableImages"],
imageFileFormat=batchData["Generation Save"][-1]["Render_Settings"]["imageFileFormat"],
enableAnimations = batchData["Generation Save"][-1]["Render_Settings"]["enableAnimations"],
animationFileFormat = batchData["Generation Save"][-1]["Render_Settings"]["animationFileFormat"],
enableAnimations=batchData["Generation Save"][-1]["Render_Settings"]["enableAnimations"],
animationFileFormat=batchData["Generation Save"][-1]["Render_Settings"]["animationFileFormat"],
enableModelsBlender = batchData["Generation Save"][-1]["Render_Settings"]["enableModelsBlender"],
modelFileFormat = batchData["Generation Save"][-1]["Render_Settings"]["modelFileFormat"],
enableModelsBlender=batchData["Generation Save"][-1]["Render_Settings"]["enableModelsBlender"],
modelFileFormat=batchData["Generation Save"][-1]["Render_Settings"]["modelFileFormat"],
enableCustomFields = batchData["Generation Save"][-1]["Render_Settings"]["enableCustomFields"],
custom_Fields = batchData["Generation Save"][-1]["Render_Settings"]["custom_Fields"],
enableCustomFields=batchData["Generation Save"][-1]["Render_Settings"]["enableCustomFields"],
custom_Fields=batchData["Generation Save"][-1]["Render_Settings"]["custom_Fields"],
cardanoMetaDataBool = batchData["Generation Save"][-1]["Render_Settings"]["cardanoMetaDataBool"],
solanaMetaDataBool = batchData["Generation Save"][-1]["Render_Settings"]["solanaMetaDataBool"],
erc721MetaData = batchData["Generation Save"][-1]["Render_Settings"]["erc721MetaData"],
cardanoMetaDataBool=batchData["Generation Save"][-1]["Render_Settings"]["cardanoMetaDataBool"],
solanaMetaDataBool=batchData["Generation Save"][-1]["Render_Settings"]["solanaMetaDataBool"],
erc721MetaData=batchData["Generation Save"][-1]["Render_Settings"]["erc721MetaData"],
cardano_description = batchData["Generation Save"][-1]["Render_Settings"]["cardano_description"],
solana_description = batchData["Generation Save"][-1]["Render_Settings"]["solana_description"],
erc721_description = batchData["Generation Save"][-1]["Render_Settings"]["erc721_description"],
cardano_description=batchData["Generation Save"][-1]["Render_Settings"]["cardano_description"],
solana_description=batchData["Generation Save"][-1]["Render_Settings"]["solana_description"],
erc721_description=batchData["Generation Save"][-1]["Render_Settings"]["erc721_description"],
enableMaterials = batchData["Generation Save"][-1]["Render_Settings"]["enableMaterials"],
materialsFile = batchData["Generation Save"][-1]["Render_Settings"]["materialsFile"],
enableMaterials=batchData["Generation Save"][-1]["Render_Settings"]["enableMaterials"],
materialsFile=batchData["Generation Save"][-1]["Render_Settings"]["materialsFile"],
fail_state = _fail_state,
failed_batch = _failed_batch,
failed_dna = _failed_dna,
failed_dna_index = _failed_dna_index
fail_state=_fail_state,
failed_batch=_failed_batch,
failed_dna=_failed_dna,
failed_dna_index=_failed_dna_index
)
Exporter.render_and_save_NFTs(input)
@ -582,51 +635,51 @@ class export_settings(bpy.types.Operator):
"#when running Blend_My_NFTs in a headless environment.\n"
"\n"
"#The name of your nft project\n"
f"nftName={ settings.nftName }\n"
f"nftName={settings.nftName}\n"
"\n"
"#NFT Collection Size\n"
f"collectionSize={ settings.collectionSize }\n"
f"collectionSize={settings.collectionSize}\n"
"\n"
"#The number of NFTs to generate per batch\n"
f"nftsPerBatch={ str(settings.nftsPerBatch) }\n"
f"nftsPerBatch={str(settings.nftsPerBatch)}\n"
"\n"
"#Save path for your NFT files\n"
f"save_path={ settings.save_path }\n"
f"save_path={settings.save_path}\n"
"\n"
"#Enable Rarity\n"
f"enableRarity={ (settings.enableRarity) }\n"
f"enableRarity={(settings.enableRarity)}\n"
"\n"
"#Enable Logic\n"
f"enableLogic={ str(settings.enableLogic) }\n"
f"enableLogicJson={ str(settings.enable_Logic_Json) }\n"
f"logicFilePath={ settings.logicFile }\n"
f"enableLogic={str(settings.enableLogic)}\n"
f"enableLogicJson={str(settings.enable_Logic_Json)}\n"
f"logicFilePath={settings.logicFile}\n"
"\n"
"#NFT Media output type(s):\n"
f"imageBool={ str(settings.imageBool) }\n"
f"imageEnum={ settings.imageEnum }\n"
f"animationBool={ str(settings.animationBool) }\n"
f"animationEnum={ settings.animationEnum }\n"
f"modelBool={ str(settings.modelBool) }\n"
f"modelEnum={ settings.modelEnum }\n"
f"imageBool={str(settings.imageBool)}\n"
f"imageEnum={settings.imageEnum}\n"
f"animationBool={str(settings.animationBool)}\n"
f"animationEnum={settings.animationEnum}\n"
f"modelBool={str(settings.modelBool)}\n"
f"modelEnum={settings.modelEnum}\n"
"\n"
"#Batch to generate\n"
f"batchToGenerate={ str(settings.batchToGenerate) }\n"
f"batchToGenerate={str(settings.batchToGenerate)}\n"
"\n"
"#Metadata Format\n"
f"cardanoMetaDataBool={ str(settings.cardanoMetaDataBool) }\n"
f"cardano_description={ settings.cardano_description }\n"
f"erc721MetaData={ str(settings.erc721MetaData) }\n"
f"erc721_description={ settings.erc721_description }\n"
f"solanaMetaDataBool={ str(settings.solanaMetaDataBool) }\n"
f"solana_description={ settings.solana_description }\n"
f"cardanoMetaDataBool={str(settings.cardanoMetaDataBool)}\n"
f"cardano_description={settings.cardano_description}\n"
f"erc721MetaData={str(settings.erc721MetaData)}\n"
f"erc721_description={settings.erc721_description}\n"
f"solanaMetaDataBool={str(settings.solanaMetaDataBool)}\n"
f"solana_description={settings.solana_description}\n"
"\n"
"#Enable Custom Fields\n"
f"enableCustomFields={ str(settings.enableCustomFields) }\n"
f"customfieldsFile={ settings.customfieldsFile }\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"
f"enableMaterials={str(settings.enableMaterials)}\n"
f"materialsFile={settings.materialsFile}\n"
)
print(output, file=config)
@ -788,7 +841,8 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel):
rows = 2
row = layout.row()
row.template_list("CUSTOM_UL_custom_metadata_fields_items", "", scn, "custom_metadata_fields", scn, "custom_metadata_fields_index", rows=rows)
row.template_list("CUSTOM_UL_custom_metadata_fields_items", "", scn, "custom_metadata_fields", scn,
"custom_metadata_fields_index", rows=rows)
col = row.column(align=True)
col.operator("custom_metadata_fields_uilist.list_action", icon='ZOOM_IN', text="").action = 'ADD'
@ -843,7 +897,6 @@ class BMNFTS_PT_Refactor(bpy.types.Panel):
layout.label(text="Ensure all batches have been created before refactoring.")
layout.label(text="Refactoring combines all batches into one easy to manage folder.")
row = layout.row()
self.layout.operator("refactor.batches", icon='FOLDER_REDIRECT', text="Refactor Batches")
@ -861,10 +914,59 @@ 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")
# 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.separator()
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()
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)
@ -913,7 +1015,7 @@ classes = (
BMNFTS_PT_GenerateNFTs,
BMNFTS_PT_Refactor,
BMNFTS_PT_Other,
) + Custom_Metadata_UIList.classes_Custom_Metadata_UIList + Logic_UIList.classes_Logic_UIList
) + Custom_Metadata_UIList.classes_Custom_Metadata_UIList + Logic_UIList.classes_Logic_UIList
def register():
@ -922,12 +1024,14 @@ def register():
bpy.types.Scene.input_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_Input_Properties)
bpy.types.Scene.custom_metadata_fields = CollectionProperty(type=Custom_Metadata_UIList.CUSTOM_custom_metadata_fields_objectCollection)
bpy.types.Scene.custom_metadata_fields = CollectionProperty(
type=Custom_Metadata_UIList.CUSTOM_custom_metadata_fields_objectCollection)
bpy.types.Scene.custom_metadata_fields_index = IntProperty()
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)

Wyświetl plik

@ -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,17 @@ 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')

Wyświetl plik

@ -82,7 +82,7 @@ def get_hierarchy():
allAttDataList = {}
for i in attributeVariants:
# Check if name follows naming conventions:
if i.count("_") > 2:
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"
@ -90,30 +90,11 @@ def get_hierarchy():
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]
return name
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)
name = getName(i)
orderRarity = getOrder_rarity(i)
try:
number = orderRarity[0]
except:
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"
@ -121,18 +102,7 @@ def get_hierarchy():
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n"
)
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"
)
eachObject = {"name": name, "number": number, "rarity": rarity}
allAttDataList[i] = eachObject
allAttDataList[i] = {"name": name, "number": number, "rarity": rarity}
return allAttDataList
variantMetaData = attributeData(attributeVariants)
@ -153,6 +123,7 @@ def get_hierarchy():
return hierarchy
def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile):
"""
Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList.
@ -163,7 +134,7 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable
# DNA random, Rarity and Logic methods:
DataDictionary = {}
def createDNArandom():
def createDNArandom(hierarchy):
"""Creates a single DNA randomly without Rarity or Logic."""
dnaStr = ""
dnaStrList = []
@ -188,24 +159,28 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable
return str(dna)
def singleCompleteDNA():
"""This function applies Rarity and Logic to a single DNA created by createDNASingle() if Rarity or Logic specified"""
"""
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()
singleDNA = createDNArandom(hierarchy)
# print("============")
# print(f"Original DNA: {singleDNA}")
if enableRarity:
singleDNA = Rarity.createDNArarity(hierarchy)
# print(f"Rarity DNA: {singleDNA}")
if enableLogic:
singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile)
# print(f"Original DNA: {singleDNA}")
# print("============\n")
singleDNA = Logic.logicafyDNAsingle(hierarchy, singleDNA, logicFile, enableRarity, enableMaterials)
# print(f"Logic DNA: {singleDNA}")
if enableMaterials:
singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile)
singleDNA = Material_Generator.apply_materials(hierarchy, singleDNA, materialsFile, enableRarity)
# print(f"Materials DNA: {singleDNA}")
# print("============\n")
return singleDNA
@ -248,6 +223,7 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable
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.
@ -303,6 +279,7 @@ def makeBatches(collectionSize, nftsPerBatch, save_path, batch_json_save_path):
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):
"""
@ -322,13 +299,14 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e
)
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}")
print(
f"{bcolors.OK}NFT DNA will be determined randomly, no special properties or parameters are applied.\n{bcolors.RESET}")
if enableRarity:
print(f"{bcolors.OK}Rarity is ON. Weights listed in .blend scene will be taken into account.\n{bcolors.RESET}")
if enableLogic:
print(f"{bcolors.OK}Logic is ON. Rules listed in {logicFile} will be taken into account.\n{bcolors.RESET}")
print(f"{bcolors.OK}Logic is ON. {len(list(logicFile.keys()))} rules detected and applied.\n{bcolors.RESET}")
time_start = time.time()
@ -345,7 +323,8 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e
Checks.raise_Error_ZeroCombinations()
if enableRarity:
Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"], os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"],
os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
except FileNotFoundError:
raise FileNotFoundError(

Wyświetl plik

@ -4,9 +4,12 @@
import bpy
import os
import ssl
import time
import json
import smtplib
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 +115,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
@ -177,12 +182,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
@ -357,20 +364,31 @@ def render_and_save_NFTs(input):
for obj in bpy.data.collections['Script_Ignore'].all_objects:
obj.select_set(True)
# Remove objects from 3D model export:
# remove_objects: list = [
# ]
#
# for obj in bpy.data.objects:
# if obj.name in remove_objects:
# obj.select_set(False)
if input.modelFileFormat == 'GLB':
bpy.ops.export_scene.gltf(filepath=f"{modelPath}.glb",
check_existing=True,
export_format='GLB',
export_keep_originals=True,
use_selection=True)
if input.modelFileFormat == 'GLTF_SEPARATE':
bpy.ops.export_scene.gltf(filepath=f"{modelPath}",
check_existing=True,
export_format='GLTF_SEPARATE',
export_keep_originals=True,
use_selection=True)
if input.modelFileFormat == 'GLTF_EMBEDDED':
bpy.ops.export_scene.gltf(filepath=f"{modelPath}.gltf",
check_existing=True,
export_format='GLTF_EMBEDDED',
export_keep_originals=True,
use_selection=True)
elif input.modelFileFormat == 'FBX':
bpy.ops.export_scene.fbx(filepath=f"{modelPath}.fbx",
@ -392,7 +410,7 @@ def render_and_save_NFTs(input):
bpy.ops.export_vox.some_data(filepath=f"{modelPath}.vox")
# Loading Animation:
loading = Loader(f'Rendering Animation {x}/{NFTs_in_Batch}...', '').start()
loading = Loader(f'Generating 3D model {x}/{NFTs_in_Batch}...', '').start()
generate_models()
loading.stop()
@ -406,20 +424,23 @@ def render_and_save_NFTs(input):
if input.cardanoMetaDataBool:
if not os.path.exists(cardanoMetadataPath):
os.makedirs(cardanoMetadataPath)
createCardanoMetadata(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict, input.custom_Fields,
createCardanoMetadata(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict,
input.custom_Fields,
input.enableCustomFields, input.cardano_description, cardanoMetadataPath)
if input.solanaMetaDataBool:
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)
createSolanaMetaData(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict,
input.custom_Fields,
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)
createErc721MetaData(name, Order_Num, full_single_dna, dnaDictionary, metadataMaterialDict,
input.custom_Fields,
input.enableCustomFields, input.erc721_description, erc721MetadataPath)
if not os.path.exists(BMNFT_metaData_Folder):
os.makedirs(BMNFT_metaData_Folder)
@ -457,3 +478,63 @@ 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:
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)
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: 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()
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)

Wyświetl plik

@ -3,8 +3,8 @@ import bpy
from main import DNA_Generator, Exporter
def send_To_Record_JSON(input, reverse_order=False):
def send_To_Record_JSON(input, reverse_order=False):
if input.enableLogic:
if input.enable_Logic_Json and input.logicFile:
input.logicFile = json.load(open(input.logicFile))
@ -24,10 +24,10 @@ def send_To_Record_JSON(input, reverse_order=False):
rule_type = item.rule_type
item_list2 = item.item_list2
input.logicFile[f"Rule-{num}"] = {
"Items-1": item_list1.split(','),
"Rule-Type": rule_type,
"Items-2": item_list2.split(',')
"IF": item_list1.split(','),
rule_type: item_list2.split(',')
}
print(rule_type)
num += 1
else:
input.logicFile = {}
@ -37,13 +37,14 @@ def send_To_Record_JSON(input, reverse_order=False):
rule_type = item.rule_type
item_list2 = item.item_list2
input.logicFile[f"Rule-{num}"] = {
"Items-1": item_list1.split(','),
"Rule-Type": rule_type,
"Items-2": item_list2.split(',')
"IF": item_list1.split(','),
rule_type: item_list2.split(',')
}
print(rule_type)
num += 1
DNA_Generator.send_To_Record_JSON( input.collectionSize,
DNA_Generator.send_To_Record_JSON(input.collectionSize,
input.nftsPerBatch,
input.save_path,
input.enableRarity,
@ -55,21 +56,23 @@ def send_To_Record_JSON(input, reverse_order=False):
input.batch_json_save_path
)
def render_and_save_NFTs(input, reverse_order=False):
def render_and_save_NFTs(input, reverse_order=False):
if input.enableCustomFields:
scn = bpy.context.scene
if reverse_order:
for i in range(scn.custom_metadata_fields_index, -1, -1):
item = scn.custom_metadata_fields[i]
if item.field_name in list(input.custom_Fields.keys()):
raise ValueError(f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
raise ValueError(
f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
else:
input.custom_Fields[item.field_name] = item.field_value
else:
for item in scn.custom_metadata_fields:
if item.field_name in list(input.custom_Fields.keys()):
raise ValueError(f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
raise ValueError(
f"A duplicate of '{item.field_name}' was found. Please ensure all Custom Metadata field Names are unique.")
else:
input.custom_Fields[item.field_name] = item.field_value

Wyświetl plik

@ -2,207 +2,283 @@
# 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, save_result
# 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 rar_selectVar(hierarchy, items_List, deconstructed_DNA):
for attribute in items_List:
a_attribute_index = getAttIndex(hierarchy, attribute)
selected_variants = items_List[attribute]
hierarchy_selected_variants = list(hierarchy[attribute])
left_over_variants = [x for x in hierarchy_selected_variants if x not in selected_variants]
if not left_over_variants:
deconstructed_DNA[int(a_attribute_index)] = "0"
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]
number_List_Of_i.append(int(number))
rarity_List_Of_i.append(float(rarity))
for x in rarity_List_Of_i:
if x == 0:
ifZeroBool = True
elif x != 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)
deconstructed_DNA[int(a_attribute_index)] = str(variantNum[0])
return deconstructed_DNA
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 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]
if "0" in var_list:
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
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 = ""
num_List1 = strip_empty_variant(num_List1)
num_List2 = strip_empty_variant(num_List2)
for a in hierarchy:
for var in list(hierarchy[a].keys()):
if var == variant:
attribute = a
break
attribute_index = list(hierarchy.keys()).index(attribute)
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]:
violates_rule = True
return violates_rule
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:
attribute_index = deconstructed_DNA.index(a)
attribute = list(hierarchy.keys())[attribute_index]
for b in hierarchy[attribute]:
if hierarchy[attribute][b]["number"] == a:
a_dna_var = b
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 '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 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(result_dict[attribute].keys()) == list(hierarchy[attribute].keys()) and not if_list_selected:
deconstructed_DNA[attribute_index] = "0"
# 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:
# 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(result_dict[a].keys())
if attribute in result_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:
violates_rule = False
return violates_rule
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])
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
return deconstructed_DNA
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
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 = 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
if var_order_num == dna_order_num: # If DNA selected Variants found inside IF list variants:
if_bool = True
break
else:
violates_rule = False
return violates_rule
continue
break
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
# 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[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(result_dict[a].keys()) == list(hierarchy[a].keys()):
full_att_bool = True
result_bool = True
break
else:
continue
break
# Rule Bool return summary:
violates_rule = False
return violates_rule
# 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
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 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 result_bool and not full_att_bool:
# violates_rule = False
return violates_rule, if_bool, result_bool, full_att_bool
# Main Function
def logicafyDNAsingle(hierarchy, singleDNA, logicFile):
def create_dicts(hierarchy, rule_list_items, result_dict_type):
# Example of output structure:
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"
]
}
}
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, 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, 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)
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_dict = create_dicts(hierarchy, logicFile[rule]["IF"], "IF")
if logicFile[rule]["Rule-Type"] == "Never With":
if never_with_Rule_Check(hierarchy, deconstructed_DNA, num_List1, num_List2):
result_dict_type = ""
if "THEN" in logicFile[rule]:
result_dict_type = "THEN"
rand_bool = bool(random.getrandbits(1))
if "NOT" in logicFile[rule]:
result_dict_type = "NOT"
if rand_bool:
deconstructed_DNA = rar_selectVar(hierarchy, items_List2, deconstructed_DNA)
result_dict = create_dicts(hierarchy, logicFile[rule][result_dict_type], result_dict_type)
if not rand_bool:
deconstructed_DNA = rar_selectVar(hierarchy, items_List1, deconstructed_DNA)
# Change 'then_bool' to 'result_bool'
violates_rule, if_bool, then_bool, full_att_bool = get_rule_break_type(hierarchy, deconstructed_DNA,
if_dict, result_dict,
result_dict_type)
if violates_rule:
# print(f"======={deconstructed_DNA} VIOLATES RULE======")
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)
deconstructed_DNA = apply_rules_to_dna(
hierarchy, deconstructed_DNA, if_dict, result_dict, result_dict_type, enableRarity
)
newDNA = reconstructDNA(deconstructed_DNA)
if newDNA != originalDNA:

Wyświetl plik

@ -7,36 +7,58 @@ import bpy
import json
import random
from .Constants import bcolors, removeList, remove_file_by_extension, save_result
def select_material(materialList):
def select_material(materialList, variant, 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:
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
break
elif x != 0:
elif b != 0:
ifZeroBool = False
if enableRarity:
try:
if ifZeroBool:
selected_material = random.choices(number_List_Of_i, k=1)
selected_material = random.choices(material_List_Of_i, k=1)
elif not ifZeroBool:
selected_material = random.choices(number_List_Of_i, weights=rarity_List_Of_i, k=1)
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:
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]
return selected_material[0], materialList
def get_variant_att_index(variant, hierarchy):
variant_attribute = None
@ -69,7 +91,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" <Normal DNA>:<Selected Material for each Variant>
@ -85,12 +107,24 @@ 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'], 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
if not complete:
deconstructed_MaterialDNA[a] = "0"
# This section is now incorrect and needs updating:
# 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])

Wyświetl plik

@ -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
@ -14,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]:
@ -24,18 +26,24 @@ 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
elif x != 0:
ifZeroBool = False
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))