Create NFT 3D Collectibles Using Blender Scripting
Tags: blender, nft, 3d, python
Published: 2021-11-21 15:21:20

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)](https://docs.blender.org/api/current/info_quickstart.html)** can be used to randomly combine a variety of traits to create 3D models programatically.  
It uses **Python** language.  

<br />

## GitHub Repo

A demo project is available.  
Check the GitHub Repogitory: **[https://github.com/hideckies/nft-collectibles-blender-python](https://github.com/hideckies/nft-collectibles-blender-python)**

<br />
<br />

## 0. Prerequisite

- Blender installed on your PC.

<br />
<br />

## 1. Create Directories

The directory tree is as follows.  

![Tree](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/tree.png)  

- **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.

<br />
<br />

## 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:  
1. 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*)
2. 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*)
3. Create a part model in the collection which be created just now.
4. Adjust the size and position to make the character look natural.
5. 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**  
![Head_1](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_head_1.png)  

File name: **nft-collectibles/parts/head/head_frog.blend**  
Collection name: **head_frog**  
![Head_2](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_head_2.png)  

<br />

### For example (Bodies):  

<br />

File name: **nft-collectibles/parts/body/body_shirt.blend**  
Collection name: **body_shirt**  
![Body_1](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_body_1.png)  

<br />
<br />

File name: **nft-collectibles/parts/head/body_zombie.blend**  
Collection name: **body_zombie**  
![Body_2](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_body_2.png)  

<br />
<br />

### For example (Misc):

File name: **nft-collectibles/parts/misc/misc.blend**  
Collection name: **misc**  
![misc](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_misc.png)  

<br />
<br />

## 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.

<br />

### 3-1. *gen_metadata.py*

This is for generating random metadata for NFT.  

```py
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()
```

<br />

### 3-2. *gen_model.py*

This is for generating models from metadata.

```py
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()
```

<br />
<br />

## 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.  

![Scripting workspace](https://blog.hdks.org/images/Create-NFT-3D-Collectibles-Using-Blender-Scripting/screenshot_scripting_workspace.jpg)  

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.  

<br />
<br />

## Complete