pysdic.create_triangle_3_axisymmetric#

create_triangle_3_axisymmetric(profile_curve=<function <lambda>>, frame=None, height_bounds=(0.0, 1.0), theta_bounds=(0.0, 6.283185307179586), n_height=10, n_theta=10, closed=False, first_diagonal=True, direct=True, uv_layout=0)[source]#

Create a 3D axisymmetric mesh Mesh using a given profile curve.

The profile curve is a function that takes a single argument (height) and returns the radius at that height. The returned radius must be strictly positive for all z in the range defined by height_bounds.

The frame parameter defines the orientation and the position of the mesh in 3D space. The axis of symmetry is aligned with the z-axis of the frame, and z=0 corresponds to the origin of the frame. The x-axis of the frame defines the direction of \(\theta=0\), and the y-axis defines the direction of \(\theta=\pi/2\).

The height_bounds parameter defines the vertical extent of the mesh, and theta_bounds defines the angular sweep around the axis. n_height and n_theta determine the number of vertices in the height and angular directions, respectively. Nodes are uniformly distributed along both directions.

Note

  • n_height and n_theta refer to the number of vertices, not segments.

For example, the following code generates a mesh of a half-cylinder whose flat face is centered on the world x-axis:

from pysdic import create_triangle_3_axisymmetric
import numpy as np

cylinder_mesh = create_triangle_3_axisymmetric(
    profile_curve=lambda z: 1.0,
    height_bounds=(-1.0, 1.0),
    theta_bounds=(-np.pi/4, np.pi/4),
    n_height=10,
    n_theta=20,
)

cylinder_mesh.visualize()
../_images/create_triangle_3_axisymmetric_example.png

Demi-cylinder mesh with the face centered on the world x-axis.#

Nodes are ordered first in theta (indexed by i_T) and then in height (indexed by i_H). So the vertex at theta index i_T and height index i_H (both starting from 0) is located at:

mesh.vertices[i_H * n_theta + i_T, :]

Each quadrilateral element is defined by the vertices:

  • \((i_T, i_H)\)

  • \((i_T + 1, i_H)\)

  • \((i_T + 1, i_H + 1)\)

  • \((i_T, i_H + 1)\)

This quadrilateral is split into two triangles.

If closed is True, the mesh is closed in the angular direction by adding one more set of elements connecting the last and first theta positions. In that case, theta_bounds are ignored and the theta starts from 0 to \(2\pi(1 - 1/n_{theta})\).

To generate a closed full cylinder:

cylinder_mesh = create_triangle_3_axisymmetric(
    profile_curve=lambda z: 1.0,
    height_bounds=(-1.0, 1.0),
    n_height=10,
    n_theta=50,
    closed=True,
)
../_images/create_triangle_3_axisymmetric_example_closed.png

Closed cylinder mesh.#

See also

  • Mesh for more information on how to visualize and manipulate the mesh.

  • Artezaru/py3dframe for details on the Frame class.

Parameters:
  • profile_curve (Callable[[float], float], optional) – A function that takes a single height coordinate z and returns a strictly positive radius. The default is a function that returns 1.0 for all z.

  • frame (Frame, optional) – The reference frame for the mesh. Defaults to the identity frame.

  • height_bounds (Tuple[float, float], optional) – The lower and upper bounds for the height coordinate. Defaults to (0.0, 1.0). The order determines the direction of vertex placement.

  • theta_bounds (Tuple[float, float], optional) – The angular sweep in radians. Defaults to (0.0, 2.0 * numpy.pi). The order determines the angular direction of vertex placement. If closed is True, this parameter is ignored.

  • n_height (int, optional) – Number of vertices along the height direction. Must be more than 1. Default is 10.

  • n_theta (int, optional) – Number of vertices along the angular direction. Must be more than 1. Default is 10.

  • closed (bool, optional) – If True, the mesh is closed in the angular direction. Default is False.

  • first_diagonal (bool, optional) – If True, the quad is split along the first diagonal (bottom-left to top-right). Default is True.

  • direct (bool, optional) – If True, triangle vertices are ordered counterclockwise for an observer looking from outside the radius. Default is True.

  • uv_layout (int, optional) – The UV mapping strategy. Default is 0.

Returns:

The generated axisymmetric mesh as a Mesh object.

Return type:

Mesh

Geometry of the mesh#

Diagonal selection#

According to the first_diagonal parameter, each quadrilateral face is split into two triangles as follows:

  • If first_diagonal is True:

    • Triangle 1: \((i_T, i_H)\), \((i_T + 1, i_H)\), \((i_T + 1, i_H + 1)\)

    • Triangle 2: \((i_T, i_H)\), \((i_T + 1, i_H + 1)\), \((i_T, i_H + 1)\)

  • If first_diagonal is False:

    • Triangle 1: \((i_T, i_H)\), \((i_T + 1, i_H)\), \((i_T, i_H + 1)\)

    • Triangle 2: \((i_T, i_H + 1)\), \((i_T + 1, i_H)\), \((i_T + 1, i_H + 1)\)

../_images/create_triangle_3_axisymmetric_diagonal.png

Diagonal selection for splitting quadrilaterals into triangles.#

Notice that if the mesh is closed, the last column of quadrilaterals connecting the last and first theta positions are added at the end of the connectivity list.

Triangle orientation#

  • If direct is True, triangles are oriented counterclockwise for an observer looking from outside the radius (See the Diagonal selection section).

  • If direct is False, triangles are oriented clockwise for an observer looking from outside the radius. Switch the order of the last two vertices in each triangle defined above.

UV Mapping#

The UV coordinates are generated based on the vertex positions in the mesh and uniformly distributed in the range [0, 1] for the OpenGL texture mapping convention (See https://learnopengl.com/Getting-started/Textures). Several UV mapping strategies are available and synthesized in the uv_layout parameter.

An image is defined by its four corners:

  • Lower-left corner : pitel with array coordinates image[height-1, 0] but OpenGL convention is (0,0) at lower-left

  • Upper-left corner : pitel with array coordinates image[0, 0] but OpenGL convention is (0,1) at upper-left

  • Lower-right corner : pitel with array coordinates image[height-1, width-1] but OpenGL convention is (1,0) at lower-right

  • Upper-right corner : pitel with array coordinates image[0, width-1] but OpenGL convention is (1,1) at upper-right

The following options are available for uv_layout and their corresponding vertex mapping:

uv_layout

Vertex lower-left corner

Vertex upper-left corner

Vertex lower-right corner

Vertex upper-right corner

0

(0, 0)

(0, n_h-1)

(n_t-1, 0)

(n_t-1, n_h-1)

1

(0, 0)

(n_t-1, 0)

(0, n_h-1)

(n_t-1, n_h-1)

2

(n_t-1, 0)

(0, 0)

(n_t-1, n_h-1)

(0, n_h-1)

3

(0, n_h-1)

(0, 0)

(n_t-1, n_h-1)

(n_t-1, 0)

4

(0, n_h-1)

(n_t-1, n_h-1)

(0, 0)

(n_t-1, 0)

5

(n_t-1, 0)

(n_t-1, n_h-1)

(0, 0)

(0, n_h-1)

6

(n_t-1, n_h-1)

(0, n_h-1)

(n_t-1, 0)

(0, 0)

7

(n_t-1, n_h-1)

(n_t-1, 0)

(0, n_h-1)

(0, 0)

Notice that for a closed mesh, the n_t - 1 becames n_t in the table above, since the a ‘virtal’ last vertex is the same as the first one.

The table above gives for the 4 corners of a image the corresponding vertex in the mesh.

../_images/create_triangle_3_axisymmetric_uv_layout.png

UV mapping strategies for different uv_layout options.#

To check the UV mapping, you can use the following code:

import numpy as np
from pysdic import create_triangle_3_axisymmetric
import cv2

cylinder_mesh = create_triangle_3_axisymmetric(
    profile_curve=lambda z: 1.0,
    height_bounds=(-1.0, 1.0),
    theta_bounds=(0, np.pi/2),
    n_height=10,
    n_theta=20,
    uv_layout=3,
)

image = cv2.imread("lena_image.png")
cylinder_mesh.visualize_texture(image)
../_images/create_triangle_3_axisymmetric_lena_texture.png

UV mapping of the Lena image on a demi-cylinder mesh with uv_layout=3.#