Mesh API reference

class motmot.Mesh(vertices, faces=None, name='')[source]
__init__(vertices, faces=None, name='')[source]

A Mesh can be constructed in three different ways:

  1. From a filename or file-like binary stream:

    mesh = Mesh("my-mesh.stl")
    
  2. From a 3D vectors array. It should have shape (n, d, 3) where n is the number of polygons and d is the number of vertices per polygon.

  3. From vertices and polygon faces.

Currently only STL files can be read directly. For other 3D files, read using meshio then convert to motmot.Mesh:

import meshio
_mesh = meshio.Mesh.read("my-mesh.ply")
mesh = Mesh(_mesh.points, _mesh.cells[0].data)

Beware however that this assumes that the mesh uses a fixed number of vertices per polygon. motmot doesn’t support mixed polygon types.

area

The total surface area. This is simply the sum of areas.

areas

Surface area of each polygon.

Returns

1D array with length len(mesh).

Note that for non co-planer (not flat) polygons, this definition becomes progressively more arbitrary as the polygons become more complicated. Different areas can be obtained for identical polygons simply by rolling a polygon’s corners so that a different corner is listed first.

property bounds: numpy.ndarray

The minimum and maximum (x, y, z) values. Equivalent to array([mesh.min, mesh.max]).

Return type

ndarray

Returns

2D array with shape (2, 3).

centers

The center of each polygon, defined as the mean of each polygon’s corners.

Returns

2D array with shape (len(mesh), 3).

closest_point(target, distance_upper_bound=None, interpolate=True)[source]

Find the nearest point on the surface of the mesh to target.

Parameters
  • target – The point(s) to query. A numpy.ndarray with shape[-1] == 3.

  • distance_upper_bound (Optional[float, None]) – A optional maximum allowed distance from target before giving up. In this case nan is returned.

  • interpolate – If true, the output will be the nearest point on the surface of the nearest polygon. Otherwise, it will only be the center of the nearest polygon.

Return type

ndarray

Returns

The nearest point(s) on the surface of the mesh. A numpy.ndarray with the same shape as target.

Note

Under extreme circumstances, namely if point lies between two very close parallel-ish surfaces with enormous polygons, then the output is not guaranteed to be optimal. It is, however, guaranteed to be better than querying the nearest vertex.

connected_polygons(initial, mask=None, polygon_map=None)[source]

Recursively walk connected polygons.

Finds the whole connected region containing the triangle arg where whole connected region here means that all triangles within that region are joined indirectly by at least one triangle.

Parameters
Returns

A connected region as a 1D bool array.

Return type

numpy.ndarray

This is implemented by navigating the polygon_map. Restrictions on connectivity can be given either with the mask parameter, which mimics removing polygons, or by overriding polygon_map and replacing values with -1 to block edges between polygons. Note that the mask only prevents the algorithm from traversing onto unmasked polygons. If a polygon is in initial then it will still be included in the output.

connected_vertices(vertex)[source]

List vertices which are directly connected to vertex by one polygon edge.

Parameters

vertex (ndarray) – A single point from vertices. A NumPy array with shape (3,).

Return type

ndarray

Returns

An (n, 3) array where n is the number of connected vertices.

Raises

KeyError – If vertex is not in vertices.

See also

This method uses vertex_map under the hood. Use vertex_map if you’re using vertex ids instead of raw vertices.

copy(deep=True)[source]

Make a shallow or deep copy of the mesh.

Parameters

deep – If true, copy the underlying vectors or vertices and faces arrays. Otherwise output will share these arrays with this mesh.

Return type

Mesh

Returns

Another mesh.

Caches of cached properties are never copied.

crop(mask, in_place=False)[source]

Return a subsample of the original mesh. Inclusion is defined by mask.

Parameters
  • mask (Union[ndarray, slice]) – Polygons to include.

  • in_place (bool) – Modify this mesh instead of making a modified copy, defaults to False.

Return type

Mesh

Returns

This mesh if in_place or a new cropped one.

A minimal usage example:

# Get only polygons with non-negative average Z-values.
cropped = mesh.crop(mesh.centers[:, 2] >= 0)

For a faces mesh this samples faces and leaves vertices untouched without copying and is equivalent to:

cropped = Mesh(mesh.vertices, mesh.faces[mask], name=mesh.name)

For a vectors based mesh this function simply samples vectors:

cropped = Mesh(mesh.vectors[mask], name=mesh.name)

Please ensure you are aware of when indexing copies in numpy if you intend to modify either the cropped or the original mesh afterwards.

When inplace is false (default), cropping can also be achieved by indexing the mesh directly:

cropped = mesh[mask]
curvature

Everything curvature related. See motmot.Curvature. Different flavours of curvature are accessible via different sub-attributes of this property such as mesh.curvature.scaleless.

dims

The overall length, width and height of the mesh. Or the difference between max and min.

displacements

The displacement from each polygon’s center to each of its neighbours’ centers.

Returns

A (len(mesh), mesh.per_polygon, 3) numpy array.

Defaults to nan when a neighbour is missing.

property dtype

The numpy.dtype of vertices and vectors.

property faces: numpy.ndarray

Indices of vertices used to construct each polygon.

Return type

ndarray

Returns

Integer array with shape (len(mesh), mesh.per_polygon).

group_connected_polygons(mask=None, polygon_map=None)[source]

Group and enumerate all polygons which are indirectly connected.

Returns

A (group_ids, group_count) pair.

Return type

(numpy.ndarray, int)

Functionally, this is equivalent to calling connected_polygons() repeatedly until every polygon is assigned a group.

To convert the output to a list of meshes use:

from rockhopper import RaggedArray
ragged = RaggedArray.group_by(mesh.vectors, *mesh.group_connected_polygons())
sub_meshes = [Mesh(i) for i in ragged]

Or if your using vertices/faces meshes:

from rockhopper import RaggedArray
ragged = RaggedArray.group_by(mesh.faces, *mesh.group_connected_polygons())
sub_meshes = [Mesh(mesh.vertices, faces) for faces in ragged]

Note that both will work irregardless of is_faces_mesh, however the mismatched implementation will be slower.

is_faces_mesh: bool = False

If true, this mesh internally uses vertices and faces. Otherwise, it uses vectors.

kdtree

A KDTree with centers as its input data.

This object powers all non-exact point lookup operations such as closest_point(). Use its query_xxx() methods for more flexible lookup.

local_maxima(heights, boundaries=True, strict=True)[source]

Find all vertices whose corresponding value in heights is greater than that of all its connected_vertices().

Parameters
  • heights (ndarray) – A per-vertex scalar to rank by.

  • boundaries (bool) – If false, ignore any vertices which touch the mesh boundary.

  • strict (bool) – If true, a vertex’s value from heights must be strictly greater than its neighbours. Otherwise, it may be greater or equal.

Return type

ndarray

Returns

The vertex ids of the vertices which are local maxima.

max

The maximum x, y and z value. Shares memory with bounds.

min

The minimum x, y and z value. Shares memory with bounds.

normals

Normals to each polygon.

Returns

2D array with shape (len(mesh), 3).

Normals point outwards provided that the polygons corners are listed in counter-clockwise order (which is the usual convention).

on_boundary(vertex)[source]

Test if a vertex touches the edge of this mesh.

Parameters

vertex (Union[ndarray, Integral]) – A 3D point in vertices. Or a single vertex ID.

Return type

bool

Returns

True if it touches, false otherwise.

Note

To check if a polygon touches a mesh edge, simply use:

any(mesh.polygon_map[polygon_id] == -1)
property path: Optional[pathlib.Path]

The filename used to open this mesh. This is set to none if this mesh was not read directly from a file.

Return type

Optional[Path, None]

Returns

Either a pathlib.Path filename or None.

property per_polygon: int

The number of corners each polygon has.

Return type

int

polygon_map

Maps each polygon to its adjacent (shares a common edge) polygons.

The format is an numpy int array with the same shape as Mesh.faces. A polygon is referenced by its position in Mesh.faces (or Mesh.vectors).

For example, assume a triangular mesh is called mesh. And suppose mesh.polygon_map[n] is [i, j, k], then:

  • The edge going from vertex 0 to vertex 1 of the triangle mesh.vectors[n] would be shared with the triangle mesh.vectors[i],

  • The edge going from vertex 1 to vertex 2 of the triangle mesh.vectors[n] would be shared with the triangle mesh.vectors[j],

  • And the edge going from vertex 2 to vertex 0 of the triangle mesh.vectors[n] would be shared with the triangle mesh.vectors[k],

Any polygons which are missing a neighbour on a particular edge (i.e. on the boundary of a non-closed mesh) use -1 as a placeholder.

reset()[source]

Invalidate all cached properties.

Use after directly writing or setting one of this mesh’s array attributes.

reverse_faces

A mapping of which polygons each vertex is in.

This mapping uses flat indices. i.e. To find all instances of vertex 123 use:

polygon_ids, corners = \
    np.divmod(self.reverse_ids[123], self.per_polygon)

Then self.faces[polygon_ids, corners] will all equal 123.

rotate(axis, theta=0, point=None)

Rotate the matrix over the given axis by the given theta (angle)

Uses the rotation_matrix() in the background.

Note

Note that the point was accidentaly inverted with the old version of the code. To get the old and incorrect behaviour simply pass -point instead of point or -numpy.array(point) if you’re passing along an array.

Parameters
  • axis (numpy.array) – Axis to rotate over (x, y, z)

  • theta (float) – Rotation angle in radians, use math.radians to convert degrees to radians if needed.

  • point (numpy.array) – Rotation point so manual translation is not required

rotate_using_matrix(rotation_matrix, point=None)[source]

Rotate inplace, the mesh using a rotation_matrix.

Internally this is just a matrix multiplication where the mesh’s vertices are post-multiplied by rotation_matrix.

static rotation_matrix(axis, theta)

Generate a rotation matrix to Rotate the matrix over the given axis by the given theta (angle)

Uses the Euler-Rodrigues formula for fast rotations.

Parameters
  • axis (numpy.array) – Axis to rotate over (x, y, z)

  • theta (float) – Rotation angle in radians, use math.radians to convert degrees to radians if needed.

save(file)[source]

Write the mesh to a file or pseudo file. Currently only STL format and compressed variants of STL (.stl.xz) are supported.

translate(translation)[source]

Move this mesh without rotating.

units

Normalised outward normals for each polygon.

Returns

2D array with shape (len(mesh), 3).

property v0

The 1st corner of each polygon. Equivalent to mesh.vectors[:, 0].

property v1

The 2nd corner of each polygon. Equivalent to mesh.vectors[:, 1].

property v2

The 3rd corner of each polygon. Equivalent to mesh.vectors[:, 2].

property vectors: numpy.ndarray

The (x, y, z) coordinates of each corner of each polygon.

Return type

ndarray

Returns

An (number of polygons, mesh.per_polygon, 3) shaped array.

vertex_counts

The number of times each vertex id appears in faces.

Returns

1D integer array with the same length as vertices.

vertex_map

A mapping from each vertex id to every other vertex that it is directly connected to.

Each row in this RaggedArray lists all the neighbours of one vertex from vertices. e.g. If mesh.vertex_map[10] is [13, 17, 19, 22] then that would imply that mesh.vertices[10] is connected to each of mesh.vertices[[13, 17, 19, 22]] by a single polygon edge.

This mapping is guaranteed to:

  • Be symmetric. If vertex A is connected to vertex B then B is connected to A.

  • Contain no self references. A will never be listed as connected to A.

  • Contains no duplicates. A will never be listed as connected to B twice.

The order in which neighbours appear is arbitrary and not guaranteed to be consistent across motmot versions.

See also

connected_vertices() if you prefer to work directly with vertices rather than vertex faces.

vertex_normals

Weighted outward normal for each vertex in vertices.

Returns

2D array with shape (len(mesh.vertices), 3).

Computed as the normalised average of the surface normals of the faces that contain that vertex. Averages are weighted by areas.

vertex_table

The lookup table behind vertex uniquifying and fast vertex lookup.

This object, a hirola.HashTable, is similar to a dict with this mesh’s unique vertices as its keys and an enumeration as its values.

To get a vertex ID (or IDs) for a given point(s) use:

ids = mesh.vertex_table[points]

This is the reciprocal of:

points = mesh.vertices[ids]

To quickly test if a vertex or vertices is in vertices use mesh.vertex_table.contains().

property vertices: numpy.ndarray

All points in the mesh with duplicity removed.

If this mesh is not originally an faces mesh, i.e. vertices had to be calculated from vectors, then this array is read-only.

Return type

ndarray

property x

The x coordinate of each vertex of each polygon. Equivalent to mesh.vectors[:, :, 0].

property y

The y coordinate of each vertex of each polygon. Equivalent to mesh.vectors[:, :, 1].

property z

The z coordinate of each vertex of each polygon. Equivalent to mesh.vectors[:, :, 2].

class motmot.Curvature(mesh)[source]

A bucket for everything curvature related.

Unlike the more traditional definition of curvature, this is defined per polygon edge rather than per vertex. Edges are defined as the shared two vertices between a polygon and a neighbour from Mesh.polygon_map.

Curvature can have several similar forms. In most cases you will want scaleless or signed for an orientation and resolution independent measurement. directional combined with geometry.inner_product() can be useful for searching for very specific shapes with a predefined orientation.

directional

Curvature’s rawest form - the cross product of each triangle’s unit normal with each of its adjacent triangles’ unit normals.

Returns

A (len(mesh), mesh.per_polygon, 3) numpy array.

This form gives a vector rather than a scalar value for each edge. The magnitude of this vector is the sin() of the angle between the two polygons and the direction is tangential to the edge between them.

magnitude

Curvature magnitudes about polygons’ edges.

Returns

A (len(mesh), mesh.per_polygon) numpy array.

Given by taking magnitudes of directional.

scaleless

The reciprocal of the radius of curvature. Or in pig’s English, a sphere with radius 10 will have scaleless curvature 1 / 10 = 0.1 throughout.

Returns

A (len(mesh), mesh.per_polygon) numpy array.

This property is independent of mesh resolution. Given by magnitude / magnitude(displacements).

signed

Signed scaleless curvature magnitudes.

Like scaleless but signed so that bumps or bulges have positive values and slots or grooves or creases have negative values.