If you read my previous post about using Python 3 to make trimmable and sculptable textured planes for Nomad Sculpt you may be wondering if we can add more realism to our renders using depth maps. And, of course, the answer is an emphatic yes – with some adjustments to the Python code, and addition of a depth map image, we can indeed add depth to the textured 3D planes we previously made. So I thought I should write a new post to describe how I did that, including putting some new Python code on the Parth3D Github repo experiments page.
So to start us off we need some texture images and corresponding depth map images. I used the ones shown below. The first is a simple spiral I drew in Affinity Designer 2 on my iPad, together with an equally simple depth map image I knocked up using Python. The second is a photo of a lovely 1968 sculpture by Mervyn Baldwin at the Newport Museum and Art Gallery in South Wales entitled ‘A column fallen under its own stone’. The depth map was made using the Looking Glass Blocks web app. Together with the Python code these are all we need.

The code on the Parth3D Github repo includes three main Python files. I’ve listed them below, with a brief description of their purpose.
cubemapdepth.py – The main code file for using Python 3 to create Wavefront OBJ and MTL files based on an image file, with the vertices scaled to a depth map.
cubemapper.py – A simple test library for creating Wavefront OBJ and MTL files textured with an image, and scaled front to back by a depth map. This is our main new code for adding depth to our image planes.
depthmaps.py – A simple Python 3 library for loading depth map images and converting them to depth data. It’s basically the one we used previously for the Photos3D library example post (so some functions are unused).
As you can see in the code below (from cubemapdepth.py) we use the depthmaps.py and cubemapper.py files as simple imports, the former mostly for converting images to depth data (based on pixel brightness) and the latter mainly for saving our Wavefront OBJ (object) and MTL (material) files. After the imports we then define the size and number of discrete width (wpoints) and height (hpoints) points we will use, and then load the images. In the code you’ll see that the column image is treated a little differently, because an RGB-D image is used. That means the texture and depth images are in one file so the code separates them and saves column.jpg as a texture image.
import depthmaps as dm
import cubemapper as cm
wpoints = int(1630 / 4)
hpoints = int(1630 / 4)
width = 100
depth = 100
thickness = 5
imfn = 'spiral.png'
depfn = 'spiral_depth.png'
rgbim, depim = dm.load(imfn, depfn)
As the code has no way of knowing the aspect ratio of the image until it’s loaded, we can now calculate that and, using the width we already defined above, calculate the height of the textured 3D model. Knowing that we can then use the cubemapper make_cube_mesh method to create all the vertices and faces we need for a cuboid, scaled to our width and height. And for the purposes of making meshes for use in Nomad Sculpt, and maybe even Blender, all of the faces are quads (i.e. faces made from four vertices) not triangles. Also, as those apps won’t use them anyway, we don’t calculate any normals – most software either calculates those itself, or it will render faces double sided.
imw, imh = rgbim.size
height = (imh / imw) * width
mesh = cm.make_cube_mesh(wpoints, hpoints, width, height, thickness)
The next bit of code is very similar to previous depth map related posts, first making the depthmap the same size as the desired horizontal and vertical vertex counts to make depth calculation easier. The depth data is then extracted from the image brightness values and those data scaled to depth values. The exception is apply_depth_array – it’s a new function, in cubemapdepth.py, that applies our calculated depths to the vertices created by the cubemapper make_cube_mesh method.
depim = depim.resize((wpoints, hpoints), Image.LANCZOS)
darr = dm.depth_image_to_array(depim, ch='red')
dm.remap_depth_array(darr, 0, depth)
dm.invert_depth_array(darr)
apply_depth_array(mesh, darr, depth, doback=True)
So now we have all of the vertex and face data for a textured cuboid, together with the texture coordinates, with depth map data used to create a realistic 3D shape. All we need do now is use the cubemapper import to write them to a Wavefront OBJ file. We also save a simple material definition file, which basically just defines the image file needed for adding texture. Note we name the files to correspond to the names used for the texture image file.
mtlfn = os.path.splitext(imfn)[0] + '.mtl'
cm.write_mtl_file(mtlfn, imfn)
objfn = os.path.splitext(imfn)[0] + '.obj'
cm.write_obj_file(objfn, mtlfn, mesh)
Note that you can edit the code to use different example images. And, of course, you can easily adapt the code to work with your own texture and depth map images. Once you’ve done that you should be able to run ‘python cubemapdepth.py’ at the commandline to generate an OBJ and MTL file to go with the texture image. Those three files can then be imported into Nomad Sculpt (or other software like Blender). The two I described earlier opened in nomad Sculpt without problem, as you can see in the image below.

One of the limitations of using depth map images is that there are only 256 levels of monochrome brightness, which means we can find steps in our imported models. Fortunately we can often resolve that issue, if we find it detracts from the desired aesthetic, using the smooth brush. I’ve put a screengrab of the column sculpture model below to illustrate that.

And below I’ve put a zoomed in screengrab of the spiral model, showing the steps in the model more clearly. For some, possibly retro, applications it could look quite nice. However, I think mostly it’s not what we want and the right side of the screengrab shows the smooth brush can be very effective. And exactly how the steps look will depend on the values you choose for ‘wpoints’ and ‘hpoints’ in the code – but for large, high resolution, options I found it can cause Pythonista to crash on a 4GB iPad Mini, which may or may not affect you.

And so there we have it, an updated way to use Python 3 to create textured digital assets for Nomad Sculpt, but now with added depth map magic. I hope you enjoy it 🙂