swiss_knight
swiss_knight

Reputation: 7911

Sweep shape along 3D path in Python

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:

with those constraints:

?

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.

Upvotes: 0

Views: 289

Answers (2)

swiss_knight
swiss_knight

Reputation: 7911

A quick update; since April 8, 2024 where PR 2206 was merged, Trimesh can now handle the banking issue natively:

Since PR 2206 Trimesh can deal with banking issue

The "con" is now that the cross section is no more constant along the path as the two triangles defining each border of the "box" are not in the same plane...

Tilting along the path with a different cross-section

Please also note that the doc for sweep_polygon in trimesh 4.2.0 specified the following parameters:

    Parameters
    ----------
    polygon : shapely.geometry.Polygon
      Profile to sweep along path
    path : (n, 3) float
      A path in 3D
    angles :  (n,) float
      Optional rotation angle relative to prior vertex
      at each vertex
    **kwargs : dict
      Passed to `triangulate_polygon`.
    Returns
    -------
    mesh : trimesh.Trimesh
      Geometry of result

whereas today, in trimesh 4.5.2, it tells:

    Parameters
    ----------
    polygon : shapely.geometry.Polygon
      Profile to sweep along path
    path : (n, 3) float
      A path in 3D
    angles : (n,) float
      Optional rotation angle relative to prior vertex
      at each vertex.
    cap
      If an open path is passed apply a cap to both ends.
    connect
      If a closed path is passed connect the sweep into
      a single watertight mesh.
    kwargs : dict
      Passed to the mesh constructor.
    **triangulation
      Passed to `triangulate_polygon`, i.e. `engine='triangle'`
    
    Returns
    -------
    mesh : trimesh.Trimesh
      Geometry of result

Upvotes: 0

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.

Upvotes: 1

Related Questions