Reformatting and preparing for debug mode

Created Helpers.py file and combined get_combinations.py, loading_animation.py, Constants.py, and Checks.py.
pull/142/head
Torrin Leonard 2022-08-24 08:56:10 -04:00
rodzic db4e4c21c1
commit cabefc04d1
12 zmienionych plików z 217 dodań i 199 usunięć

Wyświetl plik

@ -36,13 +36,11 @@ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
# Local file imports:
from main import \
Checks, \
Helpers, \
DNA_Generator, \
Exporter, \
get_combinations, \
HeadlessUtil, \
Intermediate, \
loading_animation, \
Logic, \
Material_Generator, \
Metadata, \
@ -55,12 +53,10 @@ from UILists import \
if "bpy" in locals():
modules = {
"Checks": Checks,
"Helpers": Helpers,
"DNA_Generator": DNA_Generator,
"Exporter": Exporter,
"get_combinations": get_combinations,
"HeadlessUtil": HeadlessUtil,
"loading_animation": loading_animation,
"Intermediate": Intermediate,
"Logic": Logic,
"Material_Generator": Material_Generator,
@ -75,9 +71,9 @@ if "bpy" in locals():
if i in locals():
importlib.reload(modules[i])
# ======== Persistant UI Refresh ======== #
# ======== Persistent UI Refresh ======== #
# 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
@ -92,7 +88,7 @@ def Refresh_UI(dummy1, dummy2):
global combinations
global recommended_limit
combinations = (get_combinations.get_combinations())
combinations = (Helpers.get_combinations())
recommended_limit = int(round(combinations / 2))
# Add panel classes that require refresh to this refresh_panels tuple:
@ -165,6 +161,7 @@ class BMNFTData:
sender_from: str
email_password: str
receiver_to: str
enable_debug: bool
custom_Fields: dict = None
fail_state: Any = False
@ -229,6 +226,7 @@ def getBMNFTData():
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,
enable_debug=bpy.context.scene.input_tool.enable_debug
)
return data
@ -464,7 +462,6 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup):
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")
@ -473,15 +470,16 @@ class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup):
hours: bpy.props.IntProperty(default=0, min=0)
minutes: bpy.props.IntProperty(default=0, min=0)
# Send Batch Complete Email:
emailNotificationBool: bpy.props.BoolProperty(name="Email Notifications",
description="Receive Email Notifications from Blender once a batch is finished generating")
sender_from: bpy.props.StringProperty(name="From", default="from@example.com")
email_password: bpy.props.StringProperty(name="Password", subtype='PASSWORD')
receiver_to: bpy.props.StringProperty(name="To", default="to@example.com")
enable_debug: bpy.props.BoolProperty(name="Enable Debug Mode", description="Allows you to run Blend_My_NFTs without generating any content files and includes more console information.")
# API Panel properties:
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') # Test code for future faetures
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') # Test code for future features
# ======== Main Operators ======== #
@ -602,6 +600,7 @@ class resume_failed_batch(bpy.types.Operator):
sender_from=render_settings["sender_from"],
email_password=render_settings["email_password"],
receiver_to=render_settings["receiver_to"],
enable_debug=render_settings["enable_debug"],
fail_state=_fail_state,
failed_batch=_failed_batch,
@ -1000,6 +999,9 @@ 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.prop(input_tool_scene, "enable_debug")
row = layout.row()
row = layout.row()

Wyświetl plik

@ -1,51 +0,0 @@
# Purpose:
# 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"]
def remove_file_by_extension(dirlist):
"""
Checks if a given directory list contains any of the files or file extensions listed above, if so, remove them from
list and return a clean dir list. These files interfer with BMNFTs operations and should be removed whenever dealing
with directories.
"""
if str(type(dirlist)) == "<class 'list'>":
dirlist = list(dirlist) # converts single string path to list if dir pasted as string
return_dirs = []
for directory in dirlist:
if not str(os.path.split(directory)[1]) in removeList:
return_dirs.append(directory)
return return_dirs
class bcolors:
"""
The colour of console messages.
"""
OK = '\033[92m' # GREEN
WARNING = '\033[93m' # YELLOW
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

@ -3,15 +3,13 @@
import bpy
import os
import re
import copy
import time
import json
import random
from functools import partial
from .loading_animation import Loader
from . import Rarity, Logic, Checks, Material_Generator
from .Constants import bcolors, removeList, remove_file_by_extension
from . import Rarity, Logic, Material_Generator, Helpers
from .Helpers import bcolors, Loader
def get_hierarchy():
@ -124,10 +122,10 @@ def get_hierarchy():
return hierarchy
def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile):
def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials, materialsFile, enable_debug):
"""
Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList.
"""
Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList.
"""
hierarchy = get_hierarchy()
@ -164,7 +162,6 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable
"""
singleDNA = ""
# Comments for debugging random, rarity, logic, and materials.
if not enableRarity:
singleDNA = createDNArandom(hierarchy)
# print("============")
@ -214,7 +211,7 @@ def generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enable
# Messages:
Checks.raise_Warning_collectionSize(DNAList, collectionSize)
Helpers.raise_Warning_collectionSize(DNAList, collectionSize)
# Data stored in batchDataDictionary:
DataDictionary["numNFTsGenerated"] = len(DNAList)
@ -281,7 +278,7 @@ def makeBatches(collectionSize, nftsPerBatch, save_path, batch_json_save_path):
def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, enableMaterials,
materialsFile, Blend_My_NFTs_Output, batch_json_save_path):
materialsFile, Blend_My_NFTs_Output, batch_json_save_path, enable_debug):
"""
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
@ -290,7 +287,7 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e
"""
# Checking Scene is compatible with BMNFTs:
Checks.check_Scene()
Helpers.check_Scene()
# Messages:
print(
@ -313,18 +310,18 @@ def send_To_Record_JSON(collectionSize, nftsPerBatch, save_path, enableRarity, e
def create_nft_data():
try:
DataDictionary = generateNFT_DNA(collectionSize, enableRarity, enableLogic, logicFile, enableMaterials,
materialsFile)
materialsFile, enable_debug)
NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json")
# Checks:
Checks.raise_Warning_maxNFTs(nftsPerBatch, collectionSize)
Checks.check_Duplicates(DataDictionary["DNAList"])
Checks.raise_Error_ZeroCombinations()
Helpers.raise_Warning_maxNFTs(nftsPerBatch, collectionSize)
Helpers.check_Duplicates(DataDictionary["DNAList"])
Helpers.raise_Error_ZeroCombinations()
if enableRarity:
Checks.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"],
os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
Helpers.check_Rarity(DataDictionary["hierarchy"], DataDictionary["DNAList"],
os.path.join(save_path, "Blend_My_NFTs Output/NFT_Data"))
except FileNotFoundError:
raise FileNotFoundError(

Wyświetl plik

@ -10,8 +10,8 @@ import json
import smtplib
import datetime
import platform
from .loading_animation import Loader
from .Constants import bcolors, removeList, remove_file_by_extension
from .Helpers import bcolors, Loader
from .Metadata import createCardanoMetadata, createSolanaMetaData, createErc721MetaData

Wyświetl plik

@ -1,21 +1,122 @@
# Purpose:
# The purpose of this file is to check the NFTRecord.json for duplicate NFT DNA and returns any found in the console.
# It also checks the percentage each variant is chosen in the NFTRecord, then compares it with its rarity percentage
# set in the .blend file.
# This file is provided for transparency. The accuracy of the rarity values you set in your .blend file as outlined in
# the README.md file are dependent on the maxNFTs, and the maximum number of combinations of your NFT collection.
import bpy
import os
import json
import platform
from time import sleep
from itertools import cycle
from threading import Thread
from shutil import get_terminal_size
from collections import Counter, defaultdict
from . import DNA_Generator, get_combinations
from .Constants import bcolors, removeList, remove_file_by_extension
from . import DNA_Generator
# Checks:
# ======== CONSTANTS ======== #
# This section is used for debugging, coding, or general testing purposes.
def enable_debug(enable_debug_bool):
if enable_debug_bool:
import logging
logging.basicConfig(
filename="./log.txt",
level=logging.DEBUG,
format='[%(levelname)s][%(asctime)s]\n%(message)s\n',
datefmt='%Y-%m-%d %H:%M:%S'
)
# ======== CONSTANTS ======== #
# Constants are used for storing or updating constant values that may need to be changes depending on system
# requirements and different use-cases.
removeList = [".gitignore", ".DS_Store", "desktop.ini", ".ini"]
def remove_file_by_extension(dirlist):
"""
Checks if a given directory list contains any of the files or file extensions listed above, if so, remove them from
list and return a clean dir list. These files interfer with BMNFTs operations and should be removed whenever dealing
with directories.
"""
if str(type(dirlist)) == "<class 'list'>":
dirlist = list(dirlist) # converts single string path to list if dir pasted as string
return_dirs = []
for directory in dirlist:
if not str(os.path.split(directory)[1]) in removeList:
return_dirs.append(directory)
return return_dirs
class bcolors:
"""
The colour of console messages.
"""
OK = '\033[92m' # GREEN
WARNING = '\033[93m' # YELLOW
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')
# ======== GET COMBINATIONS ======== #
# This section is used to get the number of combinations for checks and the UI display
def get_combinations():
"""
Returns "combinations", the number of all possible NFT DNA for a given Blender scene formatted to BMNFTs conventions
combinations.
"""
hierarchy = DNA_Generator.get_hierarchy()
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
return combinations
# ======== CHECKS ======== #
# This section is used to check the NFTRecord.json for duplicate NFT DNA and returns any found in the console.
# It also checks the percentage each variant is chosen in the NFTRecord, then compares it with its rarity percentage
# set in the .blend file.
# This section is provided for transparency. The accuracy of the rarity values you set in your .blend file as outlined
# in the README.md file are dependent on the maxNFTs, and the maximum number of combinations of your NFT collection.
def check_Scene(): # Not complete
"""
Checks if Blender file Scene follows the Blend_My_NFTs conventions. If not, raises error with all instances of
@ -44,6 +145,7 @@ def check_Scene(): # Not complete
# attribute_naming_conventions
def check_Rarity(hierarchy, DNAListFormatted, save_path):
"""Checks rarity percentage of each Variant, then sends it to RarityData.json in NFT_Data folder."""
@ -51,7 +153,6 @@ def check_Rarity(hierarchy, DNAListFormatted, save_path):
for i in DNAListFormatted:
DNAList.append(list(i.keys())[0])
numNFTsGenerated = len(DNAList)
numDict = defaultdict(list)
@ -90,7 +191,7 @@ def check_Rarity(hierarchy, DNAListFormatted, save_path):
if l == k:
name = fullNumName[i][k]
num = numDict[j][l]
x[name] = [(str(round(((num/numNFTsGenerated)*100), 2)) + "%"), str(num)]
x[name] = [(str(round(((num / numNFTsGenerated) * 100), 2)) + "%"), str(num)]
completeData[i] = x
@ -112,13 +213,13 @@ def check_Rarity(hierarchy, DNAListFormatted, save_path):
path = os.path.join(save_path, "RarityData.json")
print(bcolors.OK + f"Rarity Data has been saved to {path}." + bcolors.RESET)
def check_Duplicates(DNAListFormatted):
"""Checks if there are duplicates in DNAList before NFTRecord.json is sent to JSON file."""
DNAList = []
for i in DNAListFormatted:
DNAList.append(list(i.keys())[0])
duplicates = 0
seen = set()
@ -130,6 +231,7 @@ def check_Duplicates(DNAListFormatted):
print(f"\nNFTRecord.json contains {duplicates} duplicate NFT DNA.")
def check_FailedBatches(batch_json_save_path):
fail_state = False
failed_batch = None
@ -151,6 +253,7 @@ def check_FailedBatches(batch_json_save_path):
return fail_state, failed_batch, failed_dna, failed_dna_index
# Raise Errors:
def raise_Error_numBatches(maxNFTs, nftsPerBatch):
"""Checks if number of Batches is less than maxNFTs, if not raises error."""
@ -168,9 +271,10 @@ def raise_Error_numBatches(maxNFTs, nftsPerBatch):
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n{bcolors.RESET}"
)
def raise_Error_ZeroCombinations():
"""Checks if combinations is greater than 0, if so, raises error."""
if get_combinations.get_combinations() == 0:
if 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 "
@ -179,6 +283,7 @@ def raise_Error_ZeroCombinations():
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n{bcolors.RESET}"
)
def raise_Error_numBatchesGreaterThan(numBatches):
if numBatches < 1:
raise ValueError(
@ -189,8 +294,8 @@ def raise_Error_numBatchesGreaterThan(numBatches):
f"https://github.com/torrinworx/Blend_My_NFTs#blender-file-organization-and-structure\n{bcolors.RESET}"
)
# Raise Warnings:
# Raise Warnings:
def raise_Warning_maxNFTs(nftsPerBatch, collectionSize):
"""
Prints warning if nftsPerBatch is greater than collectionSize.
@ -202,6 +307,7 @@ def raise_Warning_maxNFTs(nftsPerBatch, collectionSize):
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.
@ -211,9 +317,67 @@ def raise_Warning_collectionSize(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}")
# ======== LOADING ANIMATION ======== #
# This section is used for the loading animation used in the system console.
class Loader:
def __init__(self, desc="Loading...", end="Done!", timeout=0.1):
"""
A loader-like context manager
Args:
desc (str, optional): The loader's description. Defaults to "Loading...".
end (str, optional): Final print. Defaults to "Done!".
timeout (float, optional): Sleep time between prints. Defaults to 0.1.
"""
self.desc = desc
self.end = end
self.timeout = timeout
self._thread = Thread(target=self._animate, daemon=True)
self.steps = [
" [== ]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ ==]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ == ]",
]
self.done = False
def start(self):
self._thread.start()
return self
def _animate(self):
for c in cycle(self.steps):
if self.done:
break
print(f"\r{self.desc} {c}", flush=True, end="")
sleep(self.timeout)
def __enter__(self):
self.start()
def stop(self):
self.done = True
cols = get_terminal_size((80, 20)).columns
print("\r" + " " * cols, end="", flush=True)
print(f"\r{self.end}", flush=True)
def __exit__(self, exc_type, exc_value, tb):
# handle exceptions with those variables ^
self.stop()

Wyświetl plik

@ -53,7 +53,8 @@ def send_To_Record_JSON(input, reverse_order=False):
input.enableMaterials,
input.materialsFile,
input.Blend_My_NFTs_Output,
input.batch_json_save_path
input.batch_json_save_path,
input.enable_debug,
)

Wyświetl plik

@ -5,7 +5,7 @@ import bpy
import random
import collections
from .Constants import bcolors, removeList, remove_file_by_extension, save_result
from .Helpers import bcolors, removeList, remove_file_by_extension, save_result
def reconstructDNA(deconstructedDNA):

Wyświetl plik

@ -7,7 +7,7 @@ import bpy
import json
import random
from .Constants import bcolors, removeList, remove_file_by_extension, save_result
from .Helpers import bcolors, removeList, remove_file_by_extension, save_result
def select_material(materialList, variant, enableRarity):

Wyświetl plik

@ -4,7 +4,7 @@
import bpy
import random
from .Constants import bcolors, removeList, remove_file_by_extension
from .Helpers import bcolors, removeList, remove_file_by_extension
def createDNArarity(hierarchy):

Wyświetl plik

@ -6,7 +6,7 @@ import os
import json
import shutil
from .Constants import bcolors, removeList, remove_file_by_extension
from .Helpers import bcolors, removeList, remove_file_by_extension
def reformatNFTCollection(refactor_panel_input):

Wyświetl plik

@ -1,26 +0,0 @@
import bpy
from . import DNA_Generator
def get_combinations():
"""
Returns "combinations", the number of all possible NFT DNA for a given Blender scene formatted to BMNFTs conventions
combinations.
"""
hierarchy = DNA_Generator.get_hierarchy()
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
return combinations

Wyświetl plik

@ -1,69 +0,0 @@
from itertools import cycle
from shutil import get_terminal_size
from threading import Thread
from time import sleep
class Loader:
def __init__(self, desc="Loading...", end="Done!", timeout=0.1):
"""
A loader-like context manager
Args:
desc (str, optional): The loader's description. Defaults to "Loading...".
end (str, optional): Final print. Defaults to "Done!".
timeout (float, optional): Sleep time between prints. Defaults to 0.1.
"""
self.desc = desc
self.end = end
self.timeout = timeout
self._thread = Thread(target=self._animate, daemon=True)
self.steps = [
" [== ]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ ==]",
" [ == ]",
" [ == ]",
" [ == ]",
" [ == ]",
]
self.done = False
def start(self):
self._thread.start()
return self
def _animate(self):
for c in cycle(self.steps):
if self.done:
break
print(f"\r{self.desc} {c}", flush=True, end="")
sleep(self.timeout)
def __enter__(self):
self.start()
def stop(self):
self.done = True
cols = get_terminal_size((80, 20)).columns
print("\r" + " " * cols, end="", flush=True)
print(f"\r{self.end}", flush=True)
def __exit__(self, exc_type, exc_value, tb):
# handle exceptions with those variables ^
self.stop()
if __name__ == "__main__":
with Loader("Loading with context manager..."):
for i in range(10):
sleep(0.25)
loader = Loader("Loading with object...", "That was fast!", 0.05).start()
for i in range(10):
sleep(0.25)
loader.stop()