hideckies
Posted on November 21, 2021
I wanted to create 3D characters for about 1000+ NFT Collectibles, but didn't know how to do that.
It's stupid to create all models one by one manually.
Actually, I like to use Blender for creating 3D models so also want to use it regarding to that.
When I searched on the internet, I found the solution that Blender Scripting (Python) can be used to randomly combine a variety of traits to create 3D models programatically.
It uses Python language.
GitHub Repo
A demo project is available.
Check the GitHub Repogitory: https://github.com/hideckies/nft-collectibles-blender-python
0. Prerequisite
- Blender installed on your PC.
1. Create Directories
The directory tree is as follows.
- parts --- Each 3D part included in this.
- body --- Bodies.
- head --- Heads.
- misc --- Other file (background, lights, camera, etc.).
- outputs --- Images & Metadata generated by Blender Scripting.
- scripts --- Script files.
2. Create Each Part on Blender Manually
This time, create 2 types of parts including the head, body. And misc (a file included background image, camera, light).
Steps:
- Open Blender and create a new file and remove all default objects, and save it in body or head folder. (e.g. nft-collectibles/parts/head/head_rabbit.blend)
- In Blender, create a new collection named same as the file name. (e.g. If the file name is head_rabbit.blend, the collection name is head_rabbit)
- Create a part model in the collection which be created just now.
- Adjust the size and position to make the character look natural.
- Save finally.
*This article does not explain how to modeling.
For example (Heads):
File name: nft-collectibles/parts/head/head_rabbit.blend
Collection name: head_rabbit
File name: nft-collectibles/parts/head/head_frog.blend
Collection name: head_frog
For example (Bodies):
File name: nft-collectibles/parts/body/body_shirt.blend
Collection name: body_shirt
File name: nft-collectibles/parts/head/body_zombie.blend
Collection name: body_zombie
For example (Misc):
File name: nft-collectibles/parts/misc/misc.blend
Collection name: misc
3. Create Script Files for Scripting
To create script files, open your favorite code /text editor or a text editor in the Scripting Workspace on Blender.
3-1. gen_metadata.py
This is for generating random metadata for NFT.
import bpy # The module for Blender Python
import json
import random
# This time, 100 characters will be generated.
TOTAL_CHARACTERS = 100
# Specify the directory in which generate images finally.
OUTPUTS_DIR = "c:/nft-collectibles/outputs/"
# List of parts (the file name (collection name) of each created part is included in this.)
list_body = [
"hoodie",
"overall",
"pants",
"shirt",
"tanktop",
"t_shirt",
"zombie"
]
list_head = [
"devil",
"dragon",
"frog",
"king",
"pirate",
"rabbit",
"robot",
"skull",
"warrior"
]
def random_attributes():
# Select parts randomly from lists
random_head = random.choice(list_head)
random_body = random.choice(list_body)
# Format each name for the value of attributes in metadata (e.g. "head_rabbit" -> "Head Rabbit")
random_body = random_body.replace("_", " ").title()
random_head = random_head.replace("_", " ").title()
attributes = [
{
"trait_type": "Body",
"value": random_body
},
{
"trait_type": "Head",
"value": random_head
}
]
return attributes
def main():
print("Start generating metadata...")
# Create a tempolary dict
dict_list = []
for i in range(TOTAL_CHARACTERS):
attributes = random_attributes(i)
d = { "attributes": attributes }
dict_list.append(d)
# Create a list of unique data removed duplicates
unique_list = list(map(json.loads, set(map(json.dumps, dict_list))))
# If the duplicated found, cancel scripting and try again
if len(unique_list) < TOTAL_CHARACTERS:
print("ERROR: Properties duplicate")
return
# Save metadata files (.json) to outputs folder
for i, attr_dict in enumerate(unique_list):
# Create a metadata object
obj = {
"name": "Character #" + str(i),
"description": "A collection of 3D character",
"image": "https://example.com/"+ str(i) + ".png",
"external_url": "https://example.com/",
"attributes": attr_dict["attributes"]
}
with open(OUTPUTS_DIR + str(i) + ".json", 'w') as outjson:
json.dump(obj, outjson, indent=4)
print("Generated metadata id: {}\n".format(i))
main()
3-2. gen_model.py
This is for generating models from metadata.
import bpy
import json
import random
import shutil
# This time, 100 characters will be generated.
TOTAL_CHARACTERS = 100
# Path
PARTS_DIR = "c:/nft-collectibles/parts/"
OUTPUTS_DIR = "c:/nft-collectibles/outputs/"
# Initialize the scene (deleting the all default objects)
def init():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
for col in bpy.data.collections:
bpy.data.collections.remove(col)
# Set configurations for rendering
def set_render_config():
# For example, set the rendering engine "EEVEE" (but Blender may be configured EEVEE default. if so, you don't need to do that)
r = bpy.context.scene.render
r.engine = "BLENDER_EEVEE"
r.resolution_x = 1024
r.resolution_y = 1024
# Set the output file format
r.image_settings.file_format = 'PNG'
def append_misc():
path = PARTS_DIR + "misc/" + "misc.blend/Collection/"
collection_name = "misc"
bpy.ops.wm.append(filename=collection_name, directory=path)
# Link camera to scene
cam = bpy.data.objects["camera"]
scene = bpy.context.scene
scene.camera = cam
def append_body(trait_value):
body_name = "body_" + trait_value
path = PARTS_DIR + "body/" + body_name + ".blend/Collection/"
bpy.ops.wm.append(filename=body_name, directory=path)
def append_head(trait_value):
head_name = "head_" + trait_value
path = PARTS_DIR + "head/" + head_name + ".blend/Collection/"
bpy.ops.wm.append(filename=head_name, directory=path)
def render(id):
# Render
bpy.ops.render.render(write_still=1)
# Save
bpy.data.images['Render Result'].save_render(filepath=OUTPUTS_DIR + id + ".png")
def remove_parts():
# Remove all parts except "misc"
for col in bpy.data.collections:
if col.name != "misc":
for obj in col.objects:
bpy.data.objects.remove(obj)
bpy.data.collections.remove(col)
continue
def generate(id, metadata):
for attr in metadata["attributes"]:
# Body
if attr["trait_type"] == "Body" and attr["value"] != "":
append_body(attr["value"])
# Head
if attr["trait_type"] == "Head" and attr["value"] != "":
append_head(attr["value"])
render(str(id))
# After rendered, remove all parts
remove_parts()
def main():
print("Start generating models...")
init()
set_render_config()
append_misc() # Regarding "misc", add it to the scene in advance because it is common to all characters.
# Generate character images
for i in TOTAL_CHARACTERS:
with open(OUTPUTS_DIR + str(i) + ".json", 'r') as metaJson:
data = json.load(metaJson)
generate(i, data)
print("Generated model id: {}\n".format(id))
main()
4. Run Script
After creating script files, you can run them.
Open Blender and click the Scripting workspace next to Geometry Nodes on the top of the editor.
To check the status during processing, you can open the console by clicking "Window"-> "Toggle System Console" in the top menu.
4-1. Generate metadata
Click the Open -> choose a gen_metadata.py -> click the Run Script.
As the above source code shows (gen_metadata.py, line 68-73), If the combination of data is duplicated, the process will be canceled.
In that case, you need to click the Run Script again.
After that, you can see that the metadata files such as 0.json, 1.json are generated in the outputs folder.
4-2. Generate characters
Click the Open -> choose a gen_model.py -> click the Run Script.
After that, you can see that the rendered image files such as 0.png, 1.png are generated in the outputs folder.
Complete
Posted on November 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.