# How to Convert Depth Maps to 3D Mesh Triangles Using Python and NumPy

> Learn how to convert depth maps to 3D mesh triangles using Python and NumPy. This guide details the algorithm for creating STL facets from pixel data.

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

---

**The `mcp_3d_relief` repository converts depth maps to 3D mesh triangles by first scaling pixel intensities into physical height values to create a vertex grid, then emitting STL facets for the base plate, side walls, and triangulated top surface.**

Converting a 2D grayscale depth map into a printable 3D mesh requires a structured algorithm that translates pixel intensity into physical geometry. The `bigchx/mcp_3d_relief` repository implements a complete pipeline to convert depth maps to 3D mesh triangles using NumPy for vertex calculations and a custom STL writer for facet generation. This article breaks down the algorithm implemented in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) and explains how the code generates watertight STL files suitable for 3D printing.

## Understanding the Depth Map to Mesh Pipeline

The algorithm processes depth maps in two distinct stages: vertex grid generation and facet emission. This approach ensures that the resulting mesh is mathematically consistent and watertight, connecting a flat base plate to the variable height field defined by the input image.

### Stage 1: Height-Field Vertex Grid Generation

In [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) (lines 62-66), the code initializes a 2D NumPy array called `vertices` to store the physical height of each pixel. The Z-coordinate calculation normalizes the 8-bit grayscale value (0-255) to a scale factor between 0 and 1, then multiplies it by the desired `model_thickness`:

```python
vertices[y, x] = (depth_map[y, x] / 255.0) * model_thickness

```

This transformation creates a **height-field vertex grid** where each entry represents the physical elevation of the corresponding pixel in the final 3D model.

### Stage 2: STL Facet Emission

After establishing the vertex grid, the algorithm emits triangular facets in three groups to form a solid object. The `write_facet` helper function (lines 51-65 in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py)) handles the binary STL format output, calculating normal vectors and writing vertex coordinates for each triangle.

## Step-by-Step Algorithm Implementation in relief.py

The conversion process relies on precise geometric construction to ensure the mesh is printable. The implementation follows a logical sequence: base creation, perimeter wall construction, and surface triangulation.

### Generating the Vertex Grid

The first operational step scales the input depth map into physical dimensions. The code iterates over the image dimensions, applying the height scaling formula to populate the `vertices` array. This array serves as the reference for all subsequent geometric calculations.

### Building the Base Plate

The base plate provides a flat foundation for the 3D print. In [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) (lines 74-85), the algorithm generates two triangles for each quad in the grid at a constant negative Z-coordinate equal to `-base_thickness`. These triangles cover the entire XY plane, creating a solid bottom surface.

### Constructing Side Walls

To connect the base to the height field, the algorithm generates vertical facets along the perimeter. The side wall construction (lines 87-124) creates four sets of triangles:

- Front wall (y = 0)
- Back wall (y = height-1)
- Left wall (x = 0)
- Right wall (x = width-1)

Each wall segment consists of vertical triangles connecting the base plate edge to the corresponding vertex height.

### Triangulating the Top Surface

The top surface represents the actual depth map data. For every interior grid coordinate `(x, y)`, the algorithm creates two triangles (lines 94-101) forming a quad:

- Triangle 1: `(x, y)`, `(x+1, y)`, `(x, y+1)`
- Triangle 2: `(x+1, y)`, `(x+1, y+1)`, `(x, y+1)`

This **quad-to-triangle** subdivision creates a continuous mesh surface that accurately represents the height field.

## Code Example: Generating an STL from a Depth Map

The following example demonstrates how to use the `relief` function to convert an image into a printable 3D model:

```python
import asyncio
from PIL import Image
from relief import relief

async def main():
    result = await relief(
        input_image_path="example.jpg",
        detail_level=1.0,
        model_width=50.0,
        model_thickness=5.0,
        base_thickness=2.0,
        output_dir="./output",
        skip_depth=False,
        invert_depth=False
    )
    print(f"STL saved to: {result['stl_path']}")

asyncio.run(main())

```

The [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py) file provides an HTTP endpoint that wraps this same functionality for remote processing, accepting image URLs and returning generated STL file paths.

## Summary

- The algorithm converts depth maps to 3D mesh triangles by first scaling pixel values into physical heights using `vertices[y, x] = (depth_map[y, x] / 255.0) * model_thickness`.
- The implementation in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) generates a watertight mesh consisting of a base plate, vertical side walls, and a triangulated top surface.
- Each quad in the height field is split into two triangles to create the top surface geometry.
- The `write_facet` helper function handles binary STL output with proper normal vector calculation.

## Frequently Asked Questions

### What file format does the algorithm output?

The algorithm generates **binary STL files**, a standard format for 3D printing. The `write_facet` function in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) writes triangular facets with calculated normal vectors and vertex coordinates in binary format, producing a watertight mesh suitable for slicer software.

### How does the algorithm handle the base thickness?

The base thickness is controlled by the `base_thickness` parameter. The algorithm generates a flat base plate at Z = `-base_thickness` consisting of two triangles per grid quad. This creates a solid foundation that connects to the side walls and supports the height field above.

### Can I adjust the resolution of the generated mesh?

Yes, the `detail_level` parameter controls the resolution of the depth map processing, which directly affects the vertex grid density. Higher values preserve more detail from the input image, resulting in a finer mesh with more triangles. The `model_width` parameter scales the physical dimensions while maintaining the aspect ratio.

### Is the generated mesh watertight?

Yes, the algorithm explicitly constructs a **watertight manifold mesh** by generating the base plate, four side walls, and the top surface without gaps. The side walls connect the perimeter of the base to the corresponding edges of the height field, ensuring the solid is fully enclosed and suitable for 3D printing.