Sweep shape along 3D path in Python

57 Views Asked by At

Using Python (3.10.14 at the time of writing), how could one build a 3D mesh object (which can be saved in either STL, PLY or GLB/GLTF format) using:

  • a 3D path as the sweep axis,
  • a 2D rectangular shape

with those constraints:

  • the 3D path is a true 3D path, which means that each coordinate varies in space; it's not contained in a single plane
  • the upper and lower edges of the rectangle shape must always be horizontal (which means that no banking occurs, i.e. there is no rotation of the shape during the sweep along the 3D axis)
  • the 3D path always passes perpendicularly through the center of the rectangle

?

We can consider the 3D trajectory as being composed of straight segments only (no curves). This means that two segments of the 3D axis meet at an angle, i.e. that the derivative at this point is not continuous. The resulting 3D mesh should not have holes at those locations. Therefore, the "3D join style" should be determined with a given cap style (e.g. as described here for 2 dimensions).

The 3D path is given as a numpy 3D array as follow:

import numpy as np

path = np.array([
    [ 5.6, 10.1,  3.3],
    [ 5.6, 12.4,  9.7],
    [10.2, 27.7, 17.1],
    [25.3, 34.5, 19.2],
    [55. , 28.3, 18.9],
    [80.3, 24.5, 15.4]
])

The 2D rectangular shape is given as a Shapely 2.0.3 Polygon feature:

from shapely.geometry import Polygon

polygon = Polygon([[0, 0],[1.2, 0], [1.2, 0.8], [0, 0.8], [0, 0]])

What I achieved so far

I'm currently giving Trimesh 4.2.3 (Numpy 1.26.4 being available) a try by using sweep_polygon but without success because each time the rectangle shape has to change direction, it also rotates around an axis perpendicular to the plane defined by the two egdes meeting at that vertex where the direction changes, violating the second constraint here above.

import numpy as np
from shapely.geometry import Polygon
from trimesh.creation import sweep_polygon

polygon = Polygon([[0, 0],[1.2, 0], [1.2, 0.8], [0, 0.8], [0, 0]])
path = np.array([
    [ 5.6, 10.1,  3.3],
    [ 5.6, 12.4,  9.7],
    [10.2, 27.7, 17.1],
    [25.3, 34.5, 19.2],
    [55. , 28.3, 18.9],
    [80.3, 24.5, 15.4]
])
mesh = sweep_polygon(polygon, path)

In addition, the sweep_polygon doc says:

Doesn’t handle sharp curvature well.

which is a little obscure.

Mesh rendered in meshlab. The shape's tilt is clearly visible as it rises to the right.

Mesh rendered in meshlab. The shape's tilt is clearly visible as it rises to the right.

The final goal is to run that in a Docker container on a headless server.

1

There are 1 best solutions below

1
Serge de Gosson de Varennes On

Intersting! I have been working with this kind of problems for digital twinning of stores. Here is a code that I have been using, adapted a little to your can. I ran this in Jupyter Notebook, so if you're using a GUI, you'll probably need to do some extra work:

import numpy as np
from shapely.geometry import Polygon
import trimesh

path = np.array([
    [5.6, 10.1, 3.3],
    [5.6, 12.4, 9.7],
    [10.2, 27.7, 17.1],
    [25.3, 34.5, 19.2],
    [55.0, 28.3, 18.9],
    [80.3, 24.5, 15.4]
])

rect_width = 1.2
rect_height = 0.8

def generate_mesh_vertices(path, width, height):
    vertices = []
    for point in path:
 
        vertices.append([point[0] - width / 2, point[1] - height / 2, point[2]])
        vertices.append([point[0] + width / 2, point[1] - height / 2, point[2]])
        vertices.append([point[0] + width / 2, point[1] + height / 2, point[2]])
        vertices.append([point[0] - width / 2, point[1] + height / 2, point[2]])
    return np.array(vertices)

def generate_faces_for_path(num_path_points):
    faces = []
    for i in range(num_path_points - 1):
        base_index = i * 4
        faces += [
            [base_index, base_index + 4, base_index + 1],
            [base_index + 1, base_index + 4, base_index + 5],
            [base_index + 1, base_index + 5, base_index + 2],
            [base_index + 2, base_index + 5, base_index + 6],
            [base_index + 2, base_index + 6, base_index + 3],
            [base_index + 3, base_index + 6, base_index + 7],
            [base_index + 3, base_index + 7, base_index],
            [base_index, base_index + 7, base_index + 4]
        ]
    return np.array(faces)

vertices = generate_mesh_vertices(path, rect_width, rect_height)
faces = generate_faces_for_path(len(path))

mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
mesh.export('path_mesh.stl')

import matplotlib.pyplot as plt

scene = trimesh.Scene(mesh)
scene.show()

which gives

enter image description here

You can zoom in and out, rotate etc. Stores have sharp edges, but I think i also works well with smooth ones, given that you provide the right data.