How to Debug Empty STL Output in mcp_3d_relief

Empty STL files in mcp_3d_relief almost always stem from zero-dimensional depth maps, completely flat input images, or silent exceptions during the mesh generation pipeline in relief.py.

The mcp_3d_relief repository transforms 2D images into 3D printable relief models through a two-stage process: grayscale depth-map creation followed by STL mesh generation. When debugging failed STL generation, you must verify that the depth map contains valid, non-uniform height data before the generate_stl function attempts to write facets to disk.

Understanding the STL Generation Pipeline

The conversion process in relief.py follows a strict sequence:

  1. Depth-map creation – Produces a grayscale numpy array encoding Z-height values.
  2. Mesh generation – The generate_stl function iterates over the depth map, converting pixels to vertices and calculating facet normals.
  3. File writing – The write_facet helper writes binary STL data only when normals are non-zero.

If the output file is empty (0 B or contains only solid/endsolid headers), the failure occurs in one of these four specific areas.

Common Causes of Empty STL Files

Zero-Byte Files from Empty Depth Maps

When generate_stl receives a depth map with zero height or width, the nested loops never execute, resulting in a file opened but never written to. This occurs when image loading fails or resize logic collapses dimensions to zero in the relief function (lines 21‑26).

Header-Only Files from Flat Geometry

If the depth map is completely uniform (all pixels identical), the cross-product calculation in write_facet returns a zero-length normal vector. The guard clause at lines 55‑57 skips writing these facets, leaving only the STL header and footer.

Silent Exceptions During Generation

An exception raised after open(output_path, "w") but before facet writing completes will leave an empty file on disk. The try/except block surrounding the pipeline in the relief function catches these errors and logs them to relief.log without cleaning up the partial file.

Path and Permission Errors

Missing output directories or insufficient write permissions can create empty files or silent failures. The code attempts os.makedirs(output_dir, exist_ok=True) at lines 8‑10, but permission issues may still result in empty output.

Step-by-Step Debugging Workflow

Follow this systematic approach to isolate the failure point:

1. Validate the Depth Map Dimensions

Insert diagnostic prints immediately before the generate_stl call in relief.py:

print("Depth map shape:", depth_map.shape)          # Expect (H, W) with H,W > 0

print("Depth map min/max:", depth_map.min(), depth_map.max())

If either dimension is zero, investigate the image loading and resize logic at lines 21‑26.

2. Test generate_stl in Isolation

Remove upstream variables by running the STL generator directly:

from relief import generate_stl
import numpy as np
import os
import uuid
import asyncio

dummy = np.full((10, 10), 128, dtype=np.uint8)   # Non-flat gradient

out = f"/tmp/{uuid.uuid4()}.stl"
asyncio.run(generate_stl(dummy, out))
print("STL size:", os.path.getsize(out))

If this produces a file larger than 0 B, the generator works and the issue lies in depth-map preparation.

3. Check for Flat Depth Maps

A uniform depth map cannot produce geometry. Add this guard before calling generate_stl:

if np.all(depth_map == depth_map.flat[0]):
    raise ValueError("Depth map is flat; cannot create geometry.")

4. Inspect the Log File

Runtime errors and stack traces write to relief.log (configured at lines 14‑22). Check for entries following the "Error processing image" pattern:

cat relief.log | grep -i "error"

5. Verify Output Directory Permissions

Ensure the target directory exists and is writable:

assert os.path.isdir(output_dir), f"{output_dir} missing"
assert os.access(output_dir, os.W_OK), f"{output_dir} not writable"

Practical Debug Code Examples

Minimal Reproduction with Diagnostics

This script isolates the entire pipeline and prints file statistics:

import asyncio
import os
import uuid
import numpy as np
from relief import generate_stl

async def debug():
    # Create a gradient depth map (non-uniform)

    h, w = 64, 64
    depth = np.linspace(0, 255, h * w, dtype=np.uint8).reshape(h, w)
    
    print("Depth map shape:", depth.shape)
    print("Depth map stats – min:", depth.min(), "max:", depth.max())
    
    out_path = os.path.join("output", f"{uuid.uuid4()}.stl")
    os.makedirs("output", exist_ok=True)
    
    await generate_stl(depth, out_path)
    
    print("Generated STL size:", os.path.getsize(out_path), "bytes")

asyncio.run(debug())

Adding Pipeline Sanity Checks

Insert these validation blocks inside the relief() function after depth-map creation:


# Validate dimensions

if depth_map.ndim != 2 or depth_map.shape[0] == 0 or depth_map.shape[1] == 0:
    raise ValueError("Depth map is empty or not 2-D")

# Reject uniform maps

if np.all(depth_map == depth_map.flat[0]):
    raise ValueError("Depth map is uniform – no geometry can be generated")

Reading the Log After Failure

When the STL file exists but is empty, examine the runtime logs:

$ cat relief.log | grep -i "result"
2026-02-26 14:12:03,210 - WARNING - Error processing image: ValueError('Depth map is uniform')

Key Source Files and Functions

File Purpose Critical Sections
relief.py Main pipeline orchestrating image loading, depth-map generation, and STL output. generate_depth_map (L25‑L49), generate_stl (L52‑L66), write_facet (L151‑L165), exception handling (L56‑L60)
server.py HTTP API wrapper that forwards requests to the relief module; propagates same errors. Internal calls to relief() function
relief.log Runtime log file capturing INFO/WARNING messages and stack traces for post-mortem analysis. Configured at module level (L14‑L22)

Summary

  • Empty STL files (0 B) indicate the depth map has zero dimensions or the loop never iterated.
  • Header-only STL files contain no facets because the depth map is completely flat, causing write_facet to skip all geometry.
  • Silent failures leave empty files when exceptions occur after opening the output stream but before writing; check relief.log for stack traces.
  • Always validate depth-map shape and variance before calling generate_stl to ensure non-zero geometry can be calculated.
  • Verify directory permissions and os.makedirs behavior to prevent write failures.

Frequently Asked Questions

Why is my STL file only 84 bytes (just the header)?

This occurs when the depth map is uniform (all pixels identical). Inside write_facet (lines 151‑165), the normal vector calculation yields a zero vector for flat surfaces, triggering the guard clause that skips facet writing. Ensure your input image has contrast and variation in pixel values.

How do I enable verbose logging to see loop counters?

Temporarily set the root logger to DEBUG level at the top of your debug script:

import logging
logging.getLogger().setLevel(logging.DEBUG)

This exposes the per-facet loop counters and file operation details in the console output.

Can a permission error cause an empty STL file?

Yes. If output_dir lacks write permissions but the directory exists, Python may create a 0-byte file handle before raising an IOError. Verify os.access(output_dir, os.W_OK) returns True, or check that os.makedirs(output_dir, exist_ok=True) at lines 8‑10 successfully creates the path.

What does "Depth map is empty or not 2-D" mean?

This error indicates depth_map.shape contains a zero dimension (e.g., (0, 512)) or the array has fewer than two dimensions. This typically happens when image loading fails or the resize operation collapses one axis. Verify the input image path and the resize logic at lines 21‑26 in relief.py.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →