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:From a filename or file-like binary stream:
mesh = Mesh("my-mesh.stl")
From a 3D
vectors
array. It should have shape(n, d, 3)
wheren
is the number of polygons andd
is the number of vertices per polygon.
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.
- 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 toarray([mesh.min, mesh.max])
.- Return type
- 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
withshape[-1] == 3
.distance_upper_bound (
Optional
[float
,None
]) – A optional maximum allowed distance from target before giving up. In this casenan
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
- 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
initial (int or numpy.ndarray) – The polygon id(s) to start at.
mask (numpy.ndarray) – Only include regions covered by mask if specified.
polygon_map (numpy.ndarray) – An alternative polygon map to use. Defaults to
polygon_map
.
- Returns
A connected region as a 1D bool array.
- Return type
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 fromvertices
. A NumPy array with shape(3,)
.- Return type
- Returns
An
(n, 3)
array wheren
is the number of connected vertices.- Raises
See also
This method uses
vertex_map
under the hood. Usevertex_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
orvertices
andfaces
arrays. Otherwise output will share these arrays with this mesh.- Return type
- 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
- Return type
- 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 leavesvertices
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 asmesh.curvature.scaleless
.
- 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
ofvertices
andvectors
.
- property faces: numpy.ndarray¶
Indices of vertices used to construct each polygon.
- Return type
- 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
andfaces
. Otherwise, it usesvectors
.
- kdtree¶
A KDTree with
centers
as its input data.This object powers all non-exact point lookup operations such as
closest_point()
. Use itsquery_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 itsconnected_vertices()
.- Parameters
heights (
ndarray
) – A per-vertex scalar to rank by.boundaries (
bool
) – If false, ignore any vertices whichtouch 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
- Returns
The vertex ids of the vertices which are local maxima.
- 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 invertices
. Or a single vertex ID.- Return type
- 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
- Returns
Either a
pathlib.Path
filename orNone
.
- 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 inMesh.faces
(orMesh.vectors
).For example, assume a triangular mesh is called
mesh
. And supposemesh.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 trianglemesh.vectors[i]
,The edge going from vertex 1 to vertex 2 of the triangle
mesh.vectors[n]
would be shared with the trianglemesh.vectors[j]
,And the edge going from vertex 2 to vertex 0 of the triangle
mesh.vectors[n]
would be shared with the trianglemesh.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.
- 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
- 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 fromvertices
. e.g. Ifmesh.vertex_map[10]
is[13, 17, 19, 22]
then that would imply thatmesh.vertices[10]
is connected to each ofmesh.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 byareas
.
- vertex_table¶
The lookup table behind vertex uniquifying and fast vertex lookup.
This object, a
hirola.HashTable
, is similar to adict
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
usemesh.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 fromvectors
, then this array is read-only.- Return type
- 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
orsigned
for an orientation and resolution independent measurement.directional
combined withgeometry.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 curvature1 / 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)
.