Imagine that we need to build a 3D model of an object in the python programming language and then visualize it, or prepare a file for printing on a 3D printer. There are several libraries that solve these problems. Let's see how to build a 3D model from points, edges, and primitives in Python. How to perform basic 3D modeling techniques: move, rotate, merge, subtract, etc.
Recommendation: Use NSDT Designer to quickly build programmable 3D scenes.
We'll build a Menger Sponge fractal using numpy-stl, save the model to an stl file, and render the image. Along the way, we took a brief look at data structures and terminology.
All examples are provided for the Linux operating system. Code samples can be found in the GitHub repository.
1. Overview of Numpy-stl
In Numpy-stl, a polygon mesh is structured as follows:
Vertices - list of vertices. Each point is described by three numbers - coordinates in 3-dimensional space.
Next, we'll use Jupyter notebooks. Example: numpy_stl_example_01.ipynb
import numpy as np
from myplot import plot_verticles
vertices = np.array([
[-3, -3, 0],
[+3, -3, 0],
[+3, +3, 0],
[-3, +3, 0],
[+0, +0, +3]
])
plot_verticles(vertices = vertices, isosurf = False)
Although only vertices are described, you can already see what the model would look like if you connected them with triangles:
plot_verticles(vertices = vertices, isosurf = True)
It looks like the face already exists. But now we only have vertices. To create an STL file, let's describe the faces, this can be done manually, or provided by the spatial.ConvexHull function in the scipy library for this operation.
Example: numpy_stl_example_02.ipynb
import numpy as np
from scipy import spatial
from stl import mesh
from myplot import plot_mesh
vertices = np.array(
[
[-3, -3, 0],
[+3, -3, 0],
[+3, +3, 0],
[-3, +3, 0],
[+0, +0, +3]
]
)
hull = spatial.ConvexHull(vertices)
faces = hull.simplices
As a result, the faces array contains this faces description:
array([
[4, 1, 0],
[4, 2, 1],
[3, 4, 0],
[3, 4, 2],
[3, 2, 1],
[3, 1, 0]
], dtype=int32)
Faces - list of faces. Each triangular face is described by three vertices (points). In other words, the position of the point in the vertex array.
For example, the last face contains the numbers 3, 1, 0. So faces are assembled with the points of the 0, 1 and 3 elements of the vertex array:
Mesh - A set of vertices and faces that determine the shape of a polyhedral object.
myramid_mesh = mesh.Mesh(
np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype)
)
for i, f in enumerate(faces):
for j in range(3):
myramid_mesh.vectors[i][j] = vertices[f[j],:]
plot_mesh(myramid_mesh)
As can be seen from the image, one face of the pyramid is turned upside down. In the example below, the ConvexHull method is not used when constructing the fractal because it arranges the points of the faces in an arbitrary order, which would cause some faces to flip.
myramid_mesh.save('numpy_stl_example_02.stl')
To view STL files, I use a freeware program: Blender.
The spatial.convexhull method is designed to compute convex hulls, and works well with pyramids and cubes. However, in objects with cavities, due to inconsistent points, some points will be lost, and errors will occur when assembling the STL.
This is clearly visible in the 2D example: numpy_stl_example_03.ipynb
import matplotlib.pyplot as plt
from scipy import spatial
import numpy as np
points = np.array([
[0,0],
[-2,0],
[-2,2],
[0,1.5],
[2,2],
[2,0]
])
hull = spatial.ConvexHull(points)
hull.simplices contains face descriptions:
array([
[2, 1],
[2, 4],
[5, 1],
[5, 4]
], dtype=int32)
Let's draw vertices and faces:
plt.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1], 'k-')
For this case, you can find alternatives to convexhull, or manually describe the edges:
faces = np.array([
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 0]
])
plt.plot(points[:,0], points[:,1], 'o')
for simplex in faces:
plt.plot(points[simplex, 0], points[simplex, 1], 'k-')
2. Numpy-stl constructs fractals
Time to build a fractal. There is no boolean subtraction function in Numpy-stl. To construct the Menger Sponge fractal, we took the opposite approach. There are two methods:
- Build a basic cube mesh. We call it a voxel.
- Combine multiple voxels into a mesh.
We will construct a fractal from a cube, just like the constructor.
Logical explanation for constructing fractals:
Suppose the fractal face length is 1. Depth of fractal is the count of unique hole sizes. Voxel length depends on depth of the fractal, it is divided by 3 with each new level of depth.
We gonna find the voxel side at depths 1 and 2. Let's simplify the task, turning the fractal from 3 to 1-dimensional case:
If fractal level is 2, then the length of the cube side will be 1 / (3 ** 2) which is equivalent to 1/9. Let's make a set of cubes so that they filled resulting voxel cube by their location. Let's calculate holes area. Exclude voxels that are in holes. In conclusion, unite the remaining voxels in one object and save.
Example: numpy_stl_example_04.ipynb
3. Numpy-stl rendering
To render the image, we send the mesh loaded from the STL file to the plot_mesh function.
Example: numpy_stl_example_05.ipynb
Original link: numpy-stl combat 3D modeling—BimAnt