Tutorials

How to Generate 3D Assets with Leonardo AI

AM
Alfian Majid
••7 min read
How to Generate 3D Assets with Leonardo AI

Building high-quality 3D assets and textures for game engines used to take days of manual sculpting and texture painting. With Leonardo AI's modern generation platform and REST API, you can automate asset creation directly inside your asset pipeline. Whether you need stylized props, height maps, or PBR texture sets, calling Leonardo AI programmatically yields rapid results.

In this guide, we will walk through setting up the API, building prompts tailored for 3D modeling pipelines, and scripting a complete Python tool that converts simple text descriptions into clean, textured mesh elements ready for your engine.

What Will You Learn From This Guide?

By the end of this tutorial, you will have a working Python script that interfaces with Leonardo AI to generate ready-to-use game props and material depth passes. Specifically, you will master:

  • Authenticating with the Leonardo AI API using secure environment variables
  • Configuring parameter payloads for consistent 3D style control
  • Generating high-resolution PBR texture maps and depth maps
  • Automating polling and file downloads for batch pipeline generation
  • Troubleshooting API rate limits, HTTP status errors, and visual artifacts

We will build a modular script that can easily be integrated into custom Blender extensions, Unity import tools, or Unreal Engine automation plugins.

Prerequisites & What You Need

Before writing code, make sure you have your developer environment configured properly. Here is what you need on your machine:

  • Python 3.11 or newer installed on your local system
  • A active Leonardo AI API Key (available in your account settings under API Access)
  • The requests library for sending API calls
  • The pillow library for client-side image processing
  • A code editor such as VS Code or Cursor

Set up your environment variables by running the following commands in your terminal:

pip install requests pillow pydantic

export LEONARDO_API_KEY="your_actual_api_key_here"

Developer Note: Never hardcode your API key into your scripts. If you push code to a public repository, exposed credentials can lead to account suspension or unexpected billing usage.

Step-by-Step Guide to Asset Generation

Follow these steps to set up your request pipeline and build a reliable generator.

Step 1: Set Up Authentication and Payload Structures

Create a file named asset_generator.py. Start by importing the required modules and defining helper functions to read environment variables.

import os
import time
import requests
from pathlib import Path

API_KEY = os.getenv("LEONARDO_API_KEY")
BASE_URL = "https://cloud.leonardo.ai/api/rest/v1"

if not API_KEY:
    raise ValueError("LEONARDO_API_KEY environment variable is not set.")

HEADERS = {
    "accept": "application/json",
    "content-type": "application/json",
    "authorization": f"Bearer {API_KEY}"
}

Step 2: Construct the Asset Prompt and Parameters

When generating 3D props and materials, your prompt structure matters immensely. You need flat lighting, clean backgrounds, and precise mechanical or organic detail specifications.

Here is how we construct a payload for generating a game prop image with an isolated alpha background look:

def build_payload(prompt_text, width=1024, height=1024):
    return {
        "height": height,
        "width": width,
        "modelId": "e31a6756-2917-4d6f-a949-d97a003f0d4f", # Leonardo Diffusion XL
        "prompt": prompt_text,
        "negative_prompt": "blurry, low quality, shadows, complex background, text, watermark, organic noise",
        "num_images": 1,
        "guidance_scale": 7,
        "public": False,
        "alchemy": True
    }

Step 3: Trigger the Generation Request

Send a POST request to the /generations endpoint. The response returns a generationId, which you use to check the processing status.

def start_generation(payload):
    endpoint = f"{BASE_URL}/generations"
    response = requests.post(endpoint, json=payload, headers=HEADERS)
    
    if response.status_code != 200:
        raise Exception(f"API Request failed with status {response.status_code}: {response.text}")
        
    data = response.json()
    gen_id = data.get("sdGenerationJob", {}).get("generationId")
    print(f"Generation job queued successfully. ID: {gen_id}")
    return gen_id

Step 4: Poll Status and Retrieve Output

Image generation takes a few seconds to process. Write a loop that polls the status endpoint until the image is ready, then download the resulting asset.

def wait_and_download(generation_id, output_path="output.png"):
    endpoint = f"{BASE_URL}/generations/{generation_id}"
    
    for attempt in range(30):
        response = requests.get(endpoint, headers=HEADERS)
        if response.status_code == 200:
            data = response.json()
            gen_data = data.get("generations_by_pk", {})
            status = gen_data.get("status")
            
            if status == "COMPLETE":
                images = gen_data.get("generated_images", [])
                if images:
                    img_url = images[0].get("url")
                    print(f"Asset ready! Downloading from: {img_url}")
                    
                    img_data = requests.get(img_url).content
                    with open(output_path, "wb") as f:
                        f.write(img_data)
                    print(f"Saved asset to {output_path}")
                    return output_path
            elif status == "FAILED":
                raise RuntimeError("Generation job failed on Leonardo servers.")
                
        time.sleep(3)
    raise TimeoutError("Generation request timed out after 90 seconds.")

Real-World Example: Building a Game Asset

Let's tie these steps together into a full, runnable script. In this scenario, we will generate a stylized medieval shield prop, ready for texture projection or mesh fitting.

import os
import time
import requests

API_KEY = os.getenv("LEONARDO_API_KEY")
BASE_URL = "https://cloud.leonardo.ai/api/rest/v1"

HEADERS = {
    "accept": "application/json",
    "content-type": "application/json",
    "authorization": f"Bearer {API_KEY}"
}

def generate_game_prop(prompt_description, filename):
    print(f"Starting generation for: '{prompt_description}'")
    
    # Standard technical prop prompt wrapper
    full_prompt = (
        f"Stylized 3D game asset, {prompt_description}, orthographic view, "
        "isolated studio lighting, neutral solid gray background, clean geometry lines, octane render, 8k resolution"
    )
    
    payload = {
        "height": 1024,
        "width": 1024,
        "prompt": full_prompt,
        "negative_prompt": "cropped, out of frame, drop shadow, ground reflections, realistic photography, text, signature",
        "num_images": 1,
        "guidance_scale": 7.5
    }
    
    response = requests.post(f"{BASE_URL}/generations", json=payload, headers=HEADERS)
    if response.status_code != 200:
        print(f"Error starting job: {response.status_code} - {response.text}")
        return
        
    gen_id = response.json().get("sdGenerationJob", {}).get("generationId")
    print(f"Job created with ID: {gen_id}")
    
    # Poll loop
    while True:
        status_res = requests.get(f"{BASE_URL}/generations/{gen_id}", headers=HEADERS)
        if status_res.status_code == 200:
            gen_data = status_res.json().get("generations_by_pk", {})
            status = gen_data.get("status")
            
            if status == "COMPLETE":
                images = gen_data.get("generated_images", [])
                if images:
                    url = images[0].get("url")
                    img_bytes = requests.get(url).content
                    with open(filename, "wb") as f:
                        f.write(img_bytes)
                    print(f"Successfully downloaded asset to {filename}")
                break
            elif status == "FAILED":
                print("Job failed on server.")
                break
        
        time.sleep(2)

if __name__ == "__main__":
    prompt = "iron knight heater shield with gold trim and lion crest"
    generate_game_prop(prompt, "knight_shield.png")

Community Tip: When targeting specific art styles (like hand-painted or low-poly), keep your main prompt short and push specific render engine terms into the prefix. Excess fluff weakens style consistency.

How Do You Fix Common Leonardo AI Errors?

Working with image generation APIs introduces unique failure points. Here are the most frequent errors and exact fixes for each:

  • Error 401 Unauthorized: Your authorization header is missing or improperly formatted. Ensure you include the prefix Bearer before your API key.
  • Error 422 Unprocessable Entity: Sent invalid dimensions or unknown model IDs. Make sure width and height are multiples of 8, typically between 512 and 1024.
  • Error 429 Too Many Requests: You hit your API rate limit. Implement exponential backoff in your script rather than polling every second.
  • Distorted Mesh / Background Noise: The background blends into the prop. Add studio background, clean background, white backdrop to your prompt and apply shadows, environment, dirt ground to the negative prompt.
  • Inconsistent Asset Scale: Props appear too close or cut off. Add orthographic centered view, full subject view to your prompt parameters.

Security Warning: Before running high-volume asset generation jobs, set up API spend limits in your billing settings to prevent unexpected charges from run-away retry loops.

Pro Tips & Advanced Usage

To take your generation pipeline further, consider these professional optimization techniques:

  • Keep Prompts Concise: Prompt engineering boils down to three core sentences: subject description, material parameters, and render framing. Avoid conversational language.
  • Use Alpha Channel Extraction: Use Python's rembg or pillow to automatically trim solid backgrounds and convert PNGs into transparent texture masks.
  • Generate Normal Maps: Run your clean asset textures through height-to-normal algorithms using libraries like OpenCV to generate normal maps automatically.
  • Standardize Aspect Ratios: Keep textures at 1:1 ratios (1024x1024) to ensure UV mapping algorithms align cleanly without stretching.
  • Batch Asset Calls: Build a queue system using Celery or Redis to process multiple prop variations asynchronously without locking up your local script.

Now that you have automated prop generation working via Leonardo AI, here are logical next steps for expanding your technical art workflow:

  • Integrate the output files directly into Blender using Blender Python (bpy) scripting to project textures onto primitive base meshes.
  • Combine text-to-image prompts with LLM orchestrators like GPT-5.6 Sol or Claude Sonnet 5 to automatically draft matching asset descriptions from gamedev design documents.
  • Set up automated depth-map extraction pipelines to create quick heightmaps for terrain deformation in your game engine.

Share this article

About the Author

Alfian Majid

Alfian Majid

Founder & Editor-in-Chief

Solo developer and blogger from Indonesia. Runs CogitoDaily as a passion project - covering AI news, testing tools, and writing guides. Background in web development and game tech. When not writing about AI, you'll find me deep in anime or gaming.