kopia lustrzana https://github.com/torrinworx/Blend_My_NFTs
Merge pull request #87 from TheBrochacho/main
Added support for running Blend_My_NFTs in a headless environmentpull/92/head
commit
d4125a7e4f
60
README.md
60
README.md
|
@ -108,6 +108,7 @@ The YouTube tutorials use three different .blend example files. This repository
|
|||
- [Notes on Meta Data and Standards](#notes-on-meta-data-and-standards)
|
||||
- [Calculating Maximum Number of NFTs (Max Combinations)](#calculating-maximum-number-of-nfts-max-combinations)
|
||||
- [I have my NFTs, what next?](#i-have-my-nfts-what-next)
|
||||
- [Running Blend_My_NFTs Headlessly](#running-blend_my_nfts-headlessly)
|
||||
|
||||
|
||||
# Setup and Installation
|
||||
|
@ -720,5 +721,64 @@ OpenSea and other NFT marketplaces and tools might require a specific naming con
|
|||
|
||||
[Microsoft Power Toys - Power Rename](https://docs.microsoft.com/en-us/windows/powertoys/#powerrename)
|
||||
|
||||
|
||||
## Running Blend_My_NFTs Headlessly
|
||||
|
||||
If you are working with Blender in an environment where you can't use the user interface to change settings within the addon, such as Google Colab, you can instead pass in a config file containing the settings from your local instance.
|
||||
|
||||
In order to generate this config file, you can use the `Export BMNFT settings to a file` button.
|
||||
![image](https://user-images.githubusercontent.com/16054364/162890685-142ebefe-9ec1-4ff9-9f28-60e800345444.png)
|
||||
This file will be saved in the folder indicated by the `Save Path` field.
|
||||
|
||||
Once you have this config file, you can run this addon in Blender headlessly by running this command from the directory of your Blender installation:
|
||||
|
||||
On Windows
|
||||
```
|
||||
.\blender.exe --background <path to your .blend file> --python <path to Blend_My_NFTs __init__.py> -- --config-file <path to the generated config.cfg> --operation create-dna
|
||||
```
|
||||
|
||||
On Linux
|
||||
```
|
||||
./blender --background <path to your .blend file> --python <Path to Blend_My_NFTs __init__.py> -- --config-file <path to the generated config.cfg> --operation create-dna
|
||||
```
|
||||
|
||||
There are two mandatory arguments that you need to run this script from the terminal/command line:
|
||||
- Config file location
|
||||
|
||||
This argument tells Blend_My_NFTs where to find your `config.cfg` file in order to load your desired settings.
|
||||
|
||||
`--config-file`
|
||||
- Operation
|
||||
|
||||
This argument tells Blend_My_NFTs which operation you want to perform.
|
||||
|
||||
`--operation` or `-o` with one of the following three options afterwards:
|
||||
```
|
||||
create-dna
|
||||
generate-nfts
|
||||
refactor-batches
|
||||
```
|
||||
|
||||
There are also additional optional arguments that you can use:
|
||||
- Change save location
|
||||
|
||||
This argument takes priority over the save path indicated in `config.cfg`.
|
||||
|
||||
`--save-path` or `-s`
|
||||
|
||||
You can also view this information from your terminal/command line by running:
|
||||
|
||||
On Windows
|
||||
```
|
||||
.\blender.exe --background --python <path to Blend_My_NFTs __init__.py> -- --help
|
||||
```
|
||||
|
||||
On Linux
|
||||
```
|
||||
./blender --background --python <Path to Blend_My_NFTs __init__.py> -- --help
|
||||
```
|
||||
|
||||
It is important that you place the python arguments after the `--` because of how blender parses arguments from the command line. More info about blender command line arguments can be found [here](https://docs.blender.org/manual/en/3.0/advanced/command_line/arguments.html).
|
||||
|
||||
More coming soon...
|
||||
|
||||
|
|
573
__init__.py
573
__init__.py
|
@ -1,22 +1,26 @@
|
|||
bl_info = {
|
||||
"name": "Blend_My_NFTs",
|
||||
"author": "Torrin Leonard, This Cozy Studio Inc",
|
||||
"version": (3, 1, 0),
|
||||
"version": (3, 2, 0),
|
||||
"blender": (3, 1, 3),
|
||||
"location": "View3D",
|
||||
"description": "An open source, free to use Blender add-on that enables you to create thousands of unique images, animations, and 3D models.",
|
||||
"category": "Development",
|
||||
}
|
||||
|
||||
# Import handling:
|
||||
|
||||
# ======== Import handling ======== #
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import importlib
|
||||
|
||||
# "a little hacky bs" - Matthew TheBrochacho ;)
|
||||
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
if bpy in locals():
|
||||
importlib.reload(DNA_Generator)
|
||||
|
@ -25,26 +29,217 @@ if bpy in locals():
|
|||
importlib.reload(Refactorer)
|
||||
importlib.reload(get_combinations)
|
||||
importlib.reload(Checks)
|
||||
|
||||
importlib.reload(HeadlessUtil)
|
||||
else:
|
||||
from .main import \
|
||||
from main import \
|
||||
DNA_Generator, \
|
||||
Batch_Sorter, \
|
||||
Exporter, \
|
||||
Refactorer, \
|
||||
get_combinations, \
|
||||
Checks
|
||||
Checks, \
|
||||
HeadlessUtil
|
||||
|
||||
# User input Property Group:
|
||||
class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
|
||||
|
||||
# Main BMNFTS Panel properties:
|
||||
# ======== Persistant UI Refresh ======== #
|
||||
|
||||
# Used for updating text and buttons in UI panels
|
||||
combinations: int = 0
|
||||
recommended_limit: int = 0
|
||||
|
||||
@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):
|
||||
"""Makes all Blend_My_NFTs Output folder directories from save_path input."""
|
||||
Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data")
|
||||
batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data")
|
||||
|
||||
nftBatch_save_path = os.path.join(save_path, "Blend_My_NFTs Output", "Generated NFT Batches")
|
||||
|
||||
if not os.path.exists(Blend_My_NFTs_Output):
|
||||
os.makedirs(Blend_My_NFTs_Output)
|
||||
if not os.path.exists(batch_json_save_path):
|
||||
os.makedirs(batch_json_save_path)
|
||||
if not os.path.exists(nftBatch_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"
|
||||
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"
|
||||
)
|
||||
print(output)
|
||||
|
||||
args, parser = HeadlessUtil.getPythonArgs()
|
||||
|
||||
settings = bpy.context.scene.input_tool
|
||||
|
||||
# dumpSettings(settings)
|
||||
|
||||
with open(args.config_path, 'r') as f:
|
||||
configs = [line.strip() for line in f.readlines() if not (line[0] == '#' or len(line.strip()) < 1)]
|
||||
|
||||
pairs = [config.strip().split('=') for config in configs]
|
||||
|
||||
# print(pairs)
|
||||
|
||||
settings.nftName = pairs[0][1]
|
||||
settings.collectionSize = int(pairs[1][1])
|
||||
settings.nftsPerBatch = int(pairs[2][1])
|
||||
settings.save_path = pairs[3][1]
|
||||
settings.enableRarity = pairs[4][1] == 'True'
|
||||
settings.enableLogic = pairs[5][1] == 'True'
|
||||
settings.imageBool = pairs[6][1] == 'True'
|
||||
settings.imageEnum = pairs[7][1]
|
||||
settings.animationBool = pairs[8][1] == 'True'
|
||||
settings.animationEnum = pairs[9][1]
|
||||
settings.modelBool = pairs[10][1] == 'True'
|
||||
settings.modelEnum = pairs[11][1]
|
||||
settings.batchToGenerate = int(pairs[12][1])
|
||||
settings.cardanoMetaDataBool = pairs[13][1] == 'True'
|
||||
settings.cardano_description = pairs[14][1]
|
||||
settings.erc721MetaData = pairs[15][1] == 'True'
|
||||
settings.erc721_description = pairs[16][1]
|
||||
settings.solanaMetaDataBool = pairs[17][1] == 'True'
|
||||
settings.solanaDescription = pairs[18][1]
|
||||
settings.enableCustomFields = pairs[19][1] == 'True'
|
||||
settings.customfieldsFile = pairs[20][1]
|
||||
|
||||
if args.save_path:
|
||||
settings.save_path = args.save_path
|
||||
|
||||
if args.batch_number:
|
||||
settings.batchToGenerate = args.batch_number
|
||||
|
||||
# dumpSettings(settings)
|
||||
|
||||
# don't mind me, just copy-pasting code around...
|
||||
if args.operation == 'create-dna':
|
||||
nftName = settings.nftName
|
||||
maxNFTs = settings.collectionSize
|
||||
nftsPerBatch = settings.nftsPerBatch
|
||||
save_path = bpy.path.abspath(settings.save_path)
|
||||
logicFile = bpy.path.abspath(settings.logicFile)
|
||||
|
||||
enableRarity = settings.enableRarity
|
||||
enableLogic = settings.enableLogic
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
DNA_Generator.send_To_Record_JSON(maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile,
|
||||
Blend_My_NFTs_Output)
|
||||
Batch_Sorter.makeBatches(nftName, maxNFTs, nftsPerBatch, save_path, batch_json_save_path)
|
||||
|
||||
elif args.operation == 'generate-nfts':
|
||||
nftName = settings.nftName
|
||||
save_path = bpy.path.abspath(settings.save_path)
|
||||
batchToGenerate = settings.batchToGenerate
|
||||
maxNFTs = settings.collectionSize
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
enableImages = settings.imageBool
|
||||
imageFileFormat = settings.imageEnum
|
||||
|
||||
enableAnimations = settings.animationBool
|
||||
animationFileFormat = settings.animationEnum
|
||||
|
||||
enableModelsBlender = settings.modelBool
|
||||
modelFileFormat = settings.modelEnum
|
||||
|
||||
# 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, maxNFTs, batchToGenerate, batch_json_save_path, nftBatch_save_path,
|
||||
enableImages,
|
||||
imageFileFormat, enableAnimations, animationFileFormat, enableModelsBlender,
|
||||
modelFileFormat, fail_state, failed_batch, failed_dna, failed_dna_index
|
||||
)
|
||||
elif args.operation == 'refactor-batches':
|
||||
class refactorData:
|
||||
save_path = bpy.path.abspath(settings.save_path)
|
||||
|
||||
custom_Fields_File = bpy.path.abspath(settings.customfieldsFile)
|
||||
enableCustomFields = settings.enableCustomFields
|
||||
|
||||
cardanoMetaDataBool = settings.cardanoMetaDataBool
|
||||
solanaMetaDataBool = settings.solanaMetaDataBool
|
||||
erc721MetaData = settings.erc721MetaData
|
||||
|
||||
cardano_description = settings.cardano_description
|
||||
solana_description = settings.solana_description
|
||||
erc721_description = settings.erc721_description
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
Refactorer.reformatNFTCollection(refactorData)
|
||||
|
||||
|
||||
# ======== User input Property Group ======== #
|
||||
class BMNFTS_PGT_Input_Properties(bpy.types.PropertyGroup):
|
||||
|
||||
# Create NFT Data Panel:
|
||||
|
||||
nftName: bpy.props.StringProperty(name="NFT Name")
|
||||
|
||||
collectionSize: bpy.props.IntProperty(name="NFT Collection Size", default=1, min=1) # max=(combinations - offset)
|
||||
nftsPerBatch: bpy.props.IntProperty(name="NFTs Per Batch", default=1, min=1) # max=(combinations - offset)
|
||||
batchToGenerate: bpy.props.IntProperty(name="Batch To Generate", default=1, min=1) # max=(collectionSize / nftsPerBatch)
|
||||
|
||||
save_path: bpy.props.StringProperty(
|
||||
name="Save Path",
|
||||
|
@ -56,6 +251,16 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
|
|||
|
||||
enableRarity: bpy.props.BoolProperty(name="Enable Rarity")
|
||||
|
||||
enableLogic: bpy.props.BoolProperty(name="Enable Logic")
|
||||
logicFile: bpy.props.StringProperty(
|
||||
name="Logic File",
|
||||
description="Path where Logic.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",
|
||||
|
@ -65,7 +270,7 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
|
|||
('JPEG', ".JPEG", "Export NFT as JPEG")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
animationBool: bpy.props.BoolProperty(name="Animation")
|
||||
animationEnum: bpy.props.EnumProperty(
|
||||
name="Animation File Format",
|
||||
|
@ -94,24 +299,18 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
|
|||
]
|
||||
)
|
||||
|
||||
batchToGenerate: bpy.props.IntProperty(name="Batch To Generate", default=1, min=1) # max=(collectionSize / nftsPerBatch)
|
||||
|
||||
# Refactor Batches & Create Metadata Panel:
|
||||
cardanoMetaDataBool: bpy.props.BoolProperty(name="Cardano Cip")
|
||||
cardano_description: bpy.props.StringProperty(name="Cardano description")
|
||||
|
||||
solanaMetaDataBool: bpy.props.BoolProperty(name="Solana Metaplex")
|
||||
solana_description: bpy.props.StringProperty(name="Solana description")
|
||||
|
||||
erc721MetaData: bpy.props.BoolProperty(name="ERC721")
|
||||
erc721_description: bpy.props.StringProperty(name="ERC721 description")
|
||||
|
||||
# API Panel properties:
|
||||
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD')
|
||||
|
||||
# Logic:
|
||||
enableLogic: bpy.props.BoolProperty(name="Enable Logic")
|
||||
logicFile: bpy.props.StringProperty(
|
||||
name="Logic File",
|
||||
description="Path where Logic.json is located.",
|
||||
default="",
|
||||
maxlen=1024,
|
||||
subtype="FILE_PATH"
|
||||
)
|
||||
|
||||
# Custom Metadata Fields:
|
||||
enableCustomFields: bpy.props.BoolProperty(name="Enable Custom Metadata Fields")
|
||||
customfieldsFile: bpy.props.StringProperty(
|
||||
name="Custom Fields File",
|
||||
|
@ -121,51 +320,13 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup):
|
|||
subtype="FILE_PATH"
|
||||
)
|
||||
|
||||
# Cardano Custom Metadata Fields
|
||||
cardano_description: bpy.props.StringProperty(name="Cardano description")
|
||||
# Other Panel:
|
||||
|
||||
# Solana Custom Metadata Fields
|
||||
|
||||
solana_description: bpy.props.StringProperty(name="Solana description")
|
||||
|
||||
# ERC721 Custom Metadata Fields
|
||||
erc721_description: bpy.props.StringProperty(name="ERC721 description")
|
||||
# API Panel properties:
|
||||
apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD')
|
||||
|
||||
|
||||
def make_directories(save_path):
|
||||
Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data")
|
||||
batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data")
|
||||
|
||||
nftBatch_save_path = os.path.join(save_path, "Blend_My_NFTs Output", "Generated NFT Batches")
|
||||
|
||||
if not os.path.exists(Blend_My_NFTs_Output):
|
||||
os.makedirs(Blend_My_NFTs_Output)
|
||||
if not os.path.exists(batch_json_save_path):
|
||||
os.makedirs(batch_json_save_path)
|
||||
if not os.path.exists(nftBatch_save_path):
|
||||
os.makedirs(nftBatch_save_path)
|
||||
return Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path
|
||||
|
||||
|
||||
# Update NFT count:
|
||||
combinations: int = 0
|
||||
recommended_limit: int = 0
|
||||
|
||||
@persistent
|
||||
def update_combinations(dummy1, dummy2):
|
||||
global combinations
|
||||
global recommended_limit
|
||||
global offset
|
||||
|
||||
combinations = (get_combinations.get_combinations())
|
||||
recommended_limit = int(round(combinations/2))
|
||||
redraw_panel()
|
||||
|
||||
|
||||
bpy.app.handlers.depsgraph_update_post.append(update_combinations)
|
||||
|
||||
|
||||
# Main Operators:
|
||||
# ======== Main Operators ======== #
|
||||
class createData(bpy.types.Operator):
|
||||
bl_idname = 'create.data'
|
||||
bl_label = 'Create Data'
|
||||
|
@ -174,14 +335,14 @@ class createData(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
|
||||
nftName = bpy.context.scene.my_tool.nftName
|
||||
collectionSize = bpy.context.scene.my_tool.collectionSize
|
||||
nftsPerBatch = bpy.context.scene.my_tool.nftsPerBatch
|
||||
save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path)
|
||||
logicFile = bpy.path.abspath(bpy.context.scene.my_tool.logicFile)
|
||||
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)
|
||||
|
||||
enableRarity = bpy.context.scene.my_tool.enableRarity
|
||||
enableLogic = bpy.context.scene.my_tool.enableLogic
|
||||
enableRarity = bpy.context.scene.input_tool.enableRarity
|
||||
enableLogic = bpy.context.scene.input_tool.enableLogic
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
|
@ -199,21 +360,21 @@ class exportNFTs(bpy.types.Operator):
|
|||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
nftName = bpy.context.scene.my_tool.nftName
|
||||
save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path)
|
||||
batchToGenerate = bpy.context.scene.my_tool.batchToGenerate
|
||||
collectionSize = bpy.context.scene.my_tool.collectionSize
|
||||
nftName = bpy.context.scene.input_tool.nftName
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
batchToGenerate = bpy.context.scene.input_tool.batchToGenerate
|
||||
collectionSize = bpy.context.scene.input_tool.collectionSize
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
enableImages = bpy.context.scene.my_tool.imageBool
|
||||
imageFileFormat = bpy.context.scene.my_tool.imageEnum
|
||||
enableImages = bpy.context.scene.input_tool.imageBool
|
||||
imageFileFormat = bpy.context.scene.input_tool.imageEnum
|
||||
|
||||
enableAnimations = bpy.context.scene.my_tool.animationBool
|
||||
animationFileFormat = bpy.context.scene.my_tool.animationEnum
|
||||
enableAnimations = bpy.context.scene.input_tool.animationBool
|
||||
animationFileFormat = bpy.context.scene.input_tool.animationEnum
|
||||
|
||||
enableModelsBlender = bpy.context.scene.my_tool.modelBool
|
||||
modelFileFormat = bpy.context.scene.my_tool.modelEnum
|
||||
enableModelsBlender = bpy.context.scene.input_tool.modelBool
|
||||
modelFileFormat = bpy.context.scene.input_tool.modelEnum
|
||||
|
||||
# fail state variables, set to no fail due to resume_failed_batch() Operator in BMNFTS_PT_GenerateNFTs Panel
|
||||
fail_state = False
|
||||
|
@ -237,10 +398,10 @@ class resume_failed_batch(bpy.types.Operator):
|
|||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
nftName = bpy.context.scene.my_tool.nftName
|
||||
save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path)
|
||||
batchToGenerate = bpy.context.scene.my_tool.batchToGenerate
|
||||
collectionSize = bpy.context.scene.my_tool.collectionSize
|
||||
nftName = bpy.context.scene.input_tool.nftName
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
batchToGenerate = bpy.context.scene.input_tool.batchToGenerate
|
||||
collectionSize = bpy.context.scene.input_tool.collectionSize
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
fail_state, failed_batch, failed_dna, failed_dna_index = Checks.check_FailedBatches(batch_json_save_path)
|
||||
|
@ -279,18 +440,18 @@ class refactor_Batches(bpy.types.Operator):
|
|||
def execute(self, context):
|
||||
|
||||
class refactor_panel_input:
|
||||
save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path)
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
|
||||
custom_Fields_File = bpy.path.abspath(bpy.context.scene.my_tool.customfieldsFile)
|
||||
enableCustomFields = bpy.context.scene.my_tool.enableCustomFields
|
||||
custom_Fields_File = bpy.path.abspath(bpy.context.scene.input_tool.customfieldsFile)
|
||||
enableCustomFields = bpy.context.scene.input_tool.enableCustomFields
|
||||
|
||||
cardanoMetaDataBool = bpy.context.scene.my_tool.cardanoMetaDataBool
|
||||
solanaMetaDataBool = bpy.context.scene.my_tool.solanaMetaDataBool
|
||||
erc721MetaData = bpy.context.scene.my_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.my_tool.cardano_description
|
||||
solana_description = bpy.context.scene.my_tool.solana_description
|
||||
erc721_description = bpy.context.scene.my_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
|
||||
|
||||
Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path)
|
||||
|
||||
|
@ -302,7 +463,74 @@ class refactor_Batches(bpy.types.Operator):
|
|||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
|
||||
# Create Data Panel:
|
||||
class export_settings(bpy.types.Operator):
|
||||
"""Export your settings into a configuration file."""
|
||||
bl_idname = 'export.settings'
|
||||
bl_label = 'Export Settings'
|
||||
bl_description = 'Save your settings to a configuration file'
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
filename = "config.cfg"
|
||||
|
||||
settings = bpy.context.scene.input_tool
|
||||
|
||||
with open(save_path + filename, 'w') as config:
|
||||
output = (
|
||||
"#This file was auto-generated from the Blend_My_NFTs addon and is used\n"
|
||||
"#when running Blend_My_NFTs in a headless environment.\n"
|
||||
"\n"
|
||||
"#The name of your nft project\n"
|
||||
f"nftName={settings.nftName}\n"
|
||||
"\n"
|
||||
"#NFT Collection Size\n"
|
||||
f"collectionSize={settings.collectionSize}\n"
|
||||
"\n"
|
||||
"#The number of NFTs to generate per batch\n"
|
||||
f"nftsPerBatch={str(settings.nftsPerBatch)}\n"
|
||||
"\n"
|
||||
"#Save path for your NFT files\n"
|
||||
f"save_path={settings.save_path}\n"
|
||||
"\n"
|
||||
"#Enable Rarity\n"
|
||||
f"enableRarity={(settings.enableRarity)}\n"
|
||||
"\n"
|
||||
"#Enable Logic\n"
|
||||
f"enableLogic={str(settings.enableLogic)}\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"
|
||||
"\n"
|
||||
"#Batch to generate\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"
|
||||
"\n"
|
||||
"#Enable Custom Fields\n"
|
||||
f"enableCustomFields={str(settings.enableCustomFields)}\n"
|
||||
f"customfieldsFile={settings.customfieldsFile}\n"
|
||||
)
|
||||
|
||||
print(output, file=config)
|
||||
|
||||
self.report({'INFO'}, f"Saved settings to: {save_path + filename}!")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# ======== UI Panels ======== #
|
||||
class BMNFTS_PT_CreateData(bpy.types.Panel):
|
||||
bl_label = "Create NFT Data"
|
||||
bl_idname = "BMNFTS_PT_CreateData"
|
||||
|
@ -313,38 +541,37 @@ class BMNFTS_PT_CreateData(bpy.types.Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
mytool = scene.my_tool
|
||||
input_tool_scene = scene.input_tool
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "nftName")
|
||||
row.prop(input_tool_scene, "nftName")
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text=f"Maximum Number Of NFTs: {combinations}")
|
||||
layout.label(text=f"Recommended limit: {recommended_limit}")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "collectionSize")
|
||||
row.prop(input_tool_scene, "collectionSize")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "nftsPerBatch")
|
||||
row.prop(input_tool_scene, "nftsPerBatch")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "save_path")
|
||||
row.prop(input_tool_scene, "save_path")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "enableRarity")
|
||||
row.prop(input_tool_scene, "enableRarity")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "enableLogic")
|
||||
row.prop(input_tool_scene, "enableLogic")
|
||||
|
||||
if bpy.context.scene.my_tool.enableLogic:
|
||||
if bpy.context.scene.input_tool.enableLogic:
|
||||
row = layout.row()
|
||||
row.prop(mytool, "logicFile")
|
||||
row.prop(input_tool_scene, "logicFile")
|
||||
|
||||
row = layout.row()
|
||||
self.layout.operator("create.data", icon='DISCLOSURE_TRI_RIGHT', text="Create Data")
|
||||
|
||||
# Generate NFTs Panel:
|
||||
class BMNFTS_PT_GenerateNFTs(bpy.types.Panel):
|
||||
bl_label = "Generate NFTs"
|
||||
bl_idname = "BMNFTS_PT_GenerateNFTs"
|
||||
|
@ -355,30 +582,30 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
mytool = scene.my_tool
|
||||
input_tool_scene = scene.input_tool
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text="NFT Media files:")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "imageBool")
|
||||
if bpy.context.scene.my_tool.imageBool:
|
||||
row.prop(mytool, "imageEnum")
|
||||
row.prop(input_tool_scene, "imageBool")
|
||||
if bpy.context.scene.input_tool.imageBool:
|
||||
row.prop(input_tool_scene, "imageEnum")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "animationBool")
|
||||
if bpy.context.scene.my_tool.animationBool:
|
||||
row.prop(mytool, "animationEnum")
|
||||
row.prop(input_tool_scene, "animationBool")
|
||||
if bpy.context.scene.input_tool.animationBool:
|
||||
row.prop(input_tool_scene, "animationEnum")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "modelBool")
|
||||
if bpy.context.scene.my_tool.modelBool:
|
||||
row.prop(mytool, "modelEnum")
|
||||
row.prop(input_tool_scene, "modelBool")
|
||||
if bpy.context.scene.input_tool.modelBool:
|
||||
row.prop(input_tool_scene, "modelEnum")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "batchToGenerate")
|
||||
row.prop(input_tool_scene, "batchToGenerate")
|
||||
|
||||
save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path)
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data")
|
||||
batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data")
|
||||
nftBatch_save_path = os.path.join(save_path, "Blend_My_NFTs Output", "Generated NFT Batches")
|
||||
|
@ -396,9 +623,9 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel):
|
|||
if not fail_state:
|
||||
row = layout.row()
|
||||
self.layout.operator("exporter.nfts", icon='RENDER_RESULT', text="Generate NFTs")
|
||||
# Refactor Batches & create MetaData Panel:
|
||||
|
||||
class BMNFTS_PT_Refactor(bpy.types.Panel):
|
||||
bl_label = "Refactor Batches & create MetaData"
|
||||
bl_label = "Refactor Batches & Create Metadata"
|
||||
bl_idname = "BMNFTS_PT_Refactor"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
|
@ -407,54 +634,53 @@ class BMNFTS_PT_Refactor(bpy.types.Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
mytool = scene.my_tool
|
||||
input_tool_scene = scene.input_tool
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text="Meta Data format:")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "cardanoMetaDataBool")
|
||||
if bpy.context.scene.my_tool.cardanoMetaDataBool:
|
||||
row.prop(input_tool_scene, "cardanoMetaDataBool")
|
||||
if bpy.context.scene.input_tool.cardanoMetaDataBool:
|
||||
row = layout.row()
|
||||
row.prop(mytool, "cardano_description")
|
||||
row.prop(input_tool_scene, "cardano_description")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="Cardano Metadata Documentation",
|
||||
icon='URL').url = "https://cips.cardano.org/cips/cip25/"
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "solanaMetaDataBool")
|
||||
if bpy.context.scene.my_tool.solanaMetaDataBool:
|
||||
row.prop(input_tool_scene, "solanaMetaDataBool")
|
||||
if bpy.context.scene.input_tool.solanaMetaDataBool:
|
||||
row = layout.row()
|
||||
row.prop(mytool, "solana_description")
|
||||
row.prop(input_tool_scene, "solana_description")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="Solana Metadata Documentation",
|
||||
icon='URL').url = "https://docs.metaplex.com/token-metadata/specification"
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "erc721MetaData")
|
||||
if bpy.context.scene.my_tool.erc721MetaData:
|
||||
row.prop(input_tool_scene, "erc721MetaData")
|
||||
if bpy.context.scene.input_tool.erc721MetaData:
|
||||
row = layout.row()
|
||||
row.prop(mytool, "erc721_description")
|
||||
row.prop(input_tool_scene, "erc721_description")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="ERC721 Metadata Documentation",
|
||||
icon='URL').url = "https://docs.opensea.io/docs/metadata-standards"
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mytool, "enableCustomFields")
|
||||
if bpy.context.scene.my_tool.enableCustomFields:
|
||||
row.prop(input_tool_scene, "enableCustomFields")
|
||||
if bpy.context.scene.input_tool.enableCustomFields:
|
||||
row = layout.row()
|
||||
row.prop(mytool, "customfieldsFile")
|
||||
row.prop(input_tool_scene, "customfieldsFile")
|
||||
|
||||
row = layout.row()
|
||||
self.layout.operator("refactor.batches", icon='FOLDER_REDIRECT', text="Refactor Batches & Create MetaData")
|
||||
self.layout.operator("refactor.batches", icon='FOLDER_REDIRECT', text="Refactor Batches & Create Metadata")
|
||||
|
||||
# Documentation Panel:
|
||||
class BMNFTS_PT_Documentation(bpy.types.Panel):
|
||||
bl_label = "Documentation"
|
||||
bl_idname = "BMNFTS_PT_Documentation"
|
||||
class BMNFTS_PT_Other(bpy.types.Panel):
|
||||
bl_label = "Other"
|
||||
bl_idname = "BMNFTS_PT_Other"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Blend_My_NFTs'
|
||||
|
@ -462,64 +688,65 @@ class BMNFTS_PT_Documentation(bpy.types.Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
mytool = scene.my_tool
|
||||
input_tool_scene = scene.input_tool
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
layout.label(text=f"Running Blend_My_NFTs Headless:")
|
||||
|
||||
save_path = bpy.path.abspath(bpy.context.scene.input_tool.save_path)
|
||||
|
||||
if save_path and os.path.isdir(save_path):
|
||||
row = layout.row()
|
||||
self.layout.operator("export.settings", icon='FOLDER_REDIRECT', text="Export BMNFTs Settings to a File")
|
||||
else:
|
||||
row = layout.row()
|
||||
layout.label(text=f"**Set a Save Path in Create NFT Data to Export Settings")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="Documentation",
|
||||
row.operator("wm.url_open", text="Blend_My_NFTs Documentation",
|
||||
icon='URL').url = "https://github.com/torrinworx/Blend_My_NFTs"
|
||||
|
||||
# # Materials Panel:
|
||||
#
|
||||
# class BMNFTS_PT_MATERIALS_Panel(bpy.types.Panel):
|
||||
# bl_label = "Materials"
|
||||
# bl_idname = "BMNFTS_PT_MATERIALS_Panel"
|
||||
# bl_space_type = 'VIEW_3D'
|
||||
# bl_region_type = 'UI'
|
||||
# bl_category = 'Blend_My_NFTs'
|
||||
#
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
# scene = context.scene
|
||||
# mytool = scene.my_tool
|
||||
#
|
||||
|
||||
def redraw_panel():
|
||||
try:
|
||||
bpy.utils.unregister_class(BMNFTS_PT_CreateData)
|
||||
except:
|
||||
pass
|
||||
bpy.utils.register_class(BMNFTS_PT_CreateData)
|
||||
row = layout.row()
|
||||
row.operator("wm.url_open", text="YouTube Tutorials",
|
||||
icon='URL').url = "https://www.youtube.com/watch?v=ygKJYz4BjRs&list=PLuVvzaanutXcYtWmPVKu2bx83EYNxLRsX"
|
||||
|
||||
|
||||
# Register and Unregister classes from Blender:
|
||||
# ======== Blender add-on register/unregister handling ======== #
|
||||
classes = (
|
||||
BMNFTS_PGT_MyProperties,
|
||||
BMNFTS_PT_CreateData,
|
||||
BMNFTS_PT_GenerateNFTs,
|
||||
BMNFTS_PT_Refactor,
|
||||
BMNFTS_PT_Documentation,
|
||||
|
||||
# Other panels:
|
||||
# BMNFTS_PT_MATERIALS_Panel,
|
||||
# Property Group Classes:
|
||||
BMNFTS_PGT_Input_Properties,
|
||||
|
||||
# Operator Classes:
|
||||
createData,
|
||||
exportNFTs,
|
||||
resume_failed_batch,
|
||||
refactor_Batches,
|
||||
export_settings,
|
||||
|
||||
# Panel Classes:
|
||||
BMNFTS_PT_CreateData,
|
||||
BMNFTS_PT_GenerateNFTs,
|
||||
BMNFTS_PT_Refactor,
|
||||
BMNFTS_PT_Other,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_MyProperties)
|
||||
bpy.types.Scene.input_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_Input_Properties)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
del bpy.types.Scene.my_tool
|
||||
del bpy.types.Scene.input_tool
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
register()
|
||||
runAsHeadless()
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
#adding CLI arguments
|
||||
#Used this as a basis:
|
||||
#https://developer.blender.org/diffusion/B/browse/master/release/scripts/templates_py/background_job.py
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def getPythonArgs():
|
||||
|
||||
argv = sys.argv
|
||||
|
||||
if "--" not in argv:
|
||||
argv = [] # as if no args are passed
|
||||
else:
|
||||
argv = argv[argv.index("--") + 1:] # get all args after "--"
|
||||
|
||||
usage_text = (
|
||||
"Run Blend_My_NFTs headlessly from the command line\n"
|
||||
"usage:\n"
|
||||
"blender -background --python <Path to BMNFTs __init__.py> -- --config-file <path to config file>"
|
||||
)
|
||||
|
||||
parser = argparse.ArgumentParser(description=usage_text)
|
||||
|
||||
parser.add_argument("--config-file",
|
||||
dest="config_path",
|
||||
metavar='FILE',
|
||||
required=True,
|
||||
help="Provide the full file path of the config.cfg file generated from the addon"
|
||||
)
|
||||
|
||||
parser.add_argument("--operation",
|
||||
dest="operation",
|
||||
choices=['create-dna', 'generate-nfts', 'refactor-batches'],
|
||||
required=True,
|
||||
help="Choose which operation you want to perform"
|
||||
)
|
||||
|
||||
parser.add_argument("--save-path",
|
||||
dest="save_path",
|
||||
metavar='FOLDER',
|
||||
required=False,
|
||||
help="Overwrite the save path in the config file"
|
||||
)
|
||||
|
||||
parser.add_argument("--batch",
|
||||
dest="batch_number",
|
||||
required=False,
|
||||
help="Overwrite the batch number in the config file"
|
||||
)
|
||||
|
||||
return (parser.parse_args(argv), parser)
|
Ładowanie…
Reference in New Issue