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:
- Depth-map creation – Produces a grayscale
numpyarray encoding Z-height values. - Mesh generation – The
generate_stlfunction iterates over the depth map, converting pixels to vertices and calculating facet normals. - File writing – The
write_facethelper 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_facetto skip all geometry. - Silent failures leave empty files when exceptions occur after opening the output stream but before writing; check
relief.logfor stack traces. - Always validate depth-map shape and variance before calling
generate_stlto ensure non-zero geometry can be calculated. - Verify directory permissions and
os.makedirsbehavior 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →