# How to Debug Empty STL Output in mcp_3d_relief

> Fix empty STL output from mcp_3d_relief. Learn to debug zero-dimensional depth maps, flat images, and silent exceptions in relief.py for successful mesh generation.

- Repository: [bigchx/mcp_3d_relief](https://github.com/bigchx/mcp_3d_relief)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/bigchx/mcp_3d_relief/blob/main/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`](https://github.com/bigchx/mcp_3d_relief/blob/main/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`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py#L52-L66)** function iterates over the depth map, converting pixels to vertices and calculating facet normals.
3. **File writing** – The **[`write_facet`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py#L151-L165)** 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`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py#L56-L60)** 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`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py):

```python
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:

```python
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`:

```python
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:

```bash
cat relief.log | grep -i "error"

```

### 5. Verify Output Directory Permissions

Ensure the target directory exists and is writable:

```python
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:

```python
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:

```python

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

```bash
$ 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`](https://github.com/bigchx/mcp_3d_relief/blob/main/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`](https://github.com/bigchx/mcp_3d_relief/blob/main/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:

```python
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`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py).