Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/bwoodsend/motmot
A sophisticated mesh class for analysing 3D surfaces.
https://github.com/bwoodsend/motmot
Last synced: 9 days ago
JSON representation
A sophisticated mesh class for analysing 3D surfaces.
- Host: GitHub
- URL: https://github.com/bwoodsend/motmot
- Owner: bwoodsend
- License: mit
- Created: 2021-03-10T17:11:32.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-01-02T23:39:25.000Z (10 months ago)
- Last Synced: 2024-10-07T20:36:57.904Z (about 1 month ago)
- Language: Python
- Homepage: https://bwoodsend.github.io/motmot
- Size: 5.95 MB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: history/0.1.0.rst
- Contributing: CONTRIBUTING.rst
- License: LICENSE
Awesome Lists containing this project
README
# Motmot
![](https://img.shields.io/badge/python-%203.6%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%20PyInstaller-%234691C2.svg)
A sophisticated mesh class for analysing colourless [Polygon meshes] such as
an [STL file]
providing a seamless abstraction between raw *vectors* meshes or the more
efficient *vertices + faces* (a.k.a *vertices + polygons*) form.* Free software: MIT license
* Source code: https://github.com/bwoodsend/motmot/
* Releases: https://pypi.org/project/motmot/
* Documentation: https://bwoodsend.github.io/motmot/index.html### Related projects
This library focuses almost exclusively on analysing meshes.
It it highly likely that you will need to supplement it with other libraries
to read/write to a certain format or to simplify an existing mesh.* Mesh read/write:
* [numpy-stl]:
Reads and writes STL files. This is a dependency of `motmot`.
* [meshio](https://github.com/nschloe/meshio):
Reads and writes a multitude of mesh formats.
* [pymesh](https://github.com/taxpon/pymesh):
Reads and writes STL and OBJ files.* Mesh analysis:
* [PyMesh](https://github.com/PyMesh/PyMesh):
A highly sophisticated mesh library which unfortunately depends on some
rather
hairy C++ libraries, making it not generally installable.
It's not even on PyPI.
* [trimesh](https://github.com/mikedh/trimesh):
Another general purpose mesh library. This one is pure Python and focuses
strictly on triangular and preferably closed meshes.
It also brings a few readers and writers with it.
This library is very powerful.
It's quite likely that you'd be better off using it instead of `motmot`.* Mesh cleaning:
* [quad_mesh_simplify](https://github.com/jannessm/quadric-mesh-simplification):
Decimate (collapse redundant or near redundant vertices in) meshes to make
the filesize much smaller with negligible impact on quality.
* [Py_Fast-Quadric-Mesh-Simplification](https://github.com/Kramer84/Py_Fast-Quadric-Mesh-Simplification):
Another mesh decimator. This one is much faster but not on PyPI (yet).### Usage
The basic API for ``motmot`` is modelled off that of [numpy-stl] with a few
alterations.#### Initialisation
Meshes can be :
1. Constructed from scratch using a single *vectors* array.
This array should be 3D with shape ``(n, k, 3)`` where:* ``n`` is the number of polygons in the mesh,
* ``k`` is the number of corners each polygon has,
* ``3`` corresponds to having 3 axes. i.e. ``x``, ``y`` and ``z``.```python
# vectors is a (n, 3, 3) numpy array.
triangle_mesh = Mesh(vectors)# vectors is a (n, 4, 3) numpy array.
square_mesh = Mesh(vectors)
```2. Or using the more memory efficient *vertices + faces* form.
```python
# `vertices` is an array of points. It should contain no duplicates.
# `faces` is an integer array indicating which vertices are used to construct
# each polygon.
mesh = Mesh(vertices, faces)
```3. Read from an STL file. This uses [numpy-stl] under the hood.
Currently, STL is the only file format that `motmot` will read implicitly:```python
from motmot import Mesh
mesh = Mesh("some-file.stl")
```4. Read from an lzma, gzip or bzip2 compressed STL file:
```python
from motmot import Mesh# An lzma compressed STL file. Create using `xz some-file.stl` in bash.
mesh = Mesh("some-file.stl.xz")
# A gzip compressed STL file. Create using `gzip some-file.stl` in bash.
mesh = Mesh("some-file.stl.gz")
# A bzip2 compressed STL file. Create using `bzip2 some-file.stl` in bash.
mesh = Mesh("some-file.stl.bz2")
```5. Stream from any subclass of ``io.RawIOBase``.
From here you can read from arbitrary sources such as
embedded files, streams, archives or other pseudo files.
For example, the following reads an STL directly from a web request:```python
from urllib import request
from motmot import Mesh# Pull an STL file from the internet and load it without an intermediate
# temporary file.
url = "https://raw.githubusercontent.com/bwoodsend/vtkplotlib/master/" \
"vtkplotlib/data/models/rabbit/rabbit.stl"with request.urlopen(url) as req:
mesh = Mesh(req)
```#### Vertices + Faces meshes vs Vectors meshes
There are two forms of mesh.
1. A *vectors* mesh is essentially a list of polygons where
each polygon is a list of points (its corners) and
each point is an ``(x, y, z)`` triplet.
This form is simple but wasteful because points which appear in multiple
polygons are written multiple times which wastes memory and rendering time.2. A *vertices+faces* mesh takes all the unique points from a *vectors* mesh,
calling them the *vertices*, then replaces each point in *vectors* with its
index from *vertices*, calling this *faces*.
Note that *faces* is often also known as *facets* or *polygons*.Motmot makes the two forms interchangeable.
Each of *vectors*, *vertices* and *faces* are all available as attributes on all
meshes but,
depending on how a mesh is constructed,
*vectors* may be internally derived from *vertices* and *faces* or vice-versa.```python
import numpy as np
from motmot import Mesh# Define the 8 vertices in a cuboid.
vertices = np.array([
[0., 0., 0.],
[3., 0., 0.],
[0., 5., 0.],
[3., 5., 0.],
[0., 0., 9.],
[3., 0., 9.],
[0., 5., 9.],
[3., 5., 9.],
])# Define the 6 sides of a cube or cuboid.
faces = np.array([
# Draw a square using vertices[6], vertices[2], vertices[0] and vertices[4]
[6, 2, 0, 4],
# Draw a square using vertices[0], vertices[1], vertices[5] and vertices[4]
[0, 1, 5, 4],
# And so on...
[0, 2, 3, 1],
[5, 1, 3, 7],
[3, 2, 6, 7],
[4, 5, 7, 6],
])# Construct a vertices+faces mesh.
mesh = Mesh(vertices, faces)
# This attribute is set to True to signify that this was originally a faces mesh.
mesh.is_faces_mesh
# Although `vectors` can still be derived automatically.
mesh.vectors# Construct a vectors mesh.
mesh = Mesh(vertices[faces])
# This attribute is set to False to signify that this was originally a vectors
# mesh.
mesh.is_faces_mesh
# But `vertices` and `faces` can still be derived automatically.
mesh.vertices, mesh.faces
```#### Mesh properties
This is just a brief summary of what is available.
See the corresponding entry in the
[the API reference]
for more information on each property.```python
# Outward normal to each polygon:
>>> mesh.normals
array([[-45., 0., 0.],
[ -0., -27., -0.],
[ -0., -0., -15.],
[ 45., 0., -0.],
[ -0., 27., 0.],
[ 0., 0., 15.]])# Normalised (magnitude of 1.0) outward normal to each polygon:
>>> mesh.units
array([[-1., 0., 0.],
[ 0., -1., 0.],
[ 0., 0., -1.],
[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])# Area of each polygon.
>>> mesh.areas
array([45., 27., 15., 45., 27., 15.])# Total surface area (just a sum of mesh.areas).
>>> mesh.area
174.0# The number of times each vertex is used (which admittedly
# isn't particularly interesting for a cuboid):
>>> mesh.vertex_counts
array([3, 3, 3, 3, 3, 3, 3, 3], dtype=int32)# A mapping of which other vertices each vertex is directly connect to.
>>> mesh.vertex_map
RaggedArray.from_nested([
[1, 7, 3], # vertices[0] connects to vertices[[1, 7, 3]].
[2, 6, 0], # vertices[1] connects to vertices[[2, 6, 0]].
[4, 1, 3], # and so on...
[5, 0, 2],
[5, 6, 2],
[4, 7, 3],
[1, 4, 7],
[0, 5, 6],
])# Because this mesh's vertices appear the same number of times,
# this example slightly trivialises the problem. Consider instead
# a mesh with only the first three faces. Not all vertices have
# the same number of neighbours.
>>> mesh[:3].vertex_map
RaggedArray.from_nested([
[1, 3],
[2, 6, 0],
[4, 1, 3],
[0, 2, 5],
[5, 2, 6],
[3, 4],
[4, 1],
])# If you prefer to use raw vertices rather than vertex IDs then
# use the connected_vertices() method.
>>> mesh.connected_vertices(mesh.vertices[0])
array([[0., 5., 0.],
[3., 5., 9.],
[0., 0., 9.]])# Similarly, `polygon_map` maps every polygon to each of its neighbours.
# Read the first line of the following as *polygon 0 shares an edge each with
# polygons 4, 2, 1 and 5*.
>>> mesh.polygon_map
array([[4, 2, 1, 5],
[2, 3, 5, 0],
[0, 4, 3, 1],
[1, 2, 4, 5],
[2, 0, 5, 3],
[1, 3, 4, 0]])
```#### Vertex Lookup
`motmot` leverages two libraries for looking up vertices.
* [hirola.HashTable](https://hirola.readthedocs.io/en/latest/) for [exact lookup](#exact-lookup)
* [pykdtree.kdtree.KDTree](https://github.com/storpipfugl/pykdtree) for [fuzzy lookup](#approximate-lookup)##### Exact lookup
It is easy to convert vertex IDs to real vertices.
Simply pass them as indices to `mesh.vertices`.```python
>>> ids = [0, 4, 5, 2]
>>> points = mesh.vertices[ids]
>>> points
array([[0., 0., 0.],
[0., 0., 9.],
[3., 0., 9.],
[0., 5., 0.]])
```Go the other way by indexing the `vertex_table` attribute.
```python
>>> mesh.vertex_table[points]
array([0, 4, 5, 2], dtype=int64)
```Some things to be aware of:
* The `dtype` of the points queried must match `mesh.dtype`.
* As with regular floats in a regular Python `dict`,
even the smallest deviation will cause lookup to fail.```python
>>> mesh.vertex_table[[3., 0., 9.]]
5
>>> mesh.vertex_table[[3., 0, 9.00000000001]]
KeyError: 'key = array([3., 0., 9.]) is not in this table.'
```##### Approximate lookup
To find *nearest points*, `motmot` uses a [KDTree].
The API here is very shallow and it is quite likely that you may wish to
create and use `KDTree`s directly rather than use `motmot`'s methods.A KDTree, fitted to `mesh.centers` (the middle of each polygon),
is found at the `mesh.kdtree` attribute.Given a set of points defined as:
```python
points = np.array([[2., 3.5, 4.2], [2.3, 4.2, 1.1]], mesh.dtype)
```Find the nearest point on the mesh surface to each point:
```python
>>> mesh.closest_point(points)
array([[3. , 3.5, 4.2],
[2.3, 4.2, 0. ]])
```
Or to restrict the output to only `mesh.centers` without interpolating between
them:```python
>>> mesh.closest_point(points, interpolate=False)
array([[3. , 2.5, 4.5],
[1.5, 2.5, 0. ]])
```For anything else, use `mesh.kdtree` directly.
#### Laziness
A `motmot.Mesh` *lazy loads* its properties using a backport of
[@functools.cached_property].
This allows them to be calculated when only you need them so that no time is
ever wasted calculating something which you do not use.
Take for example, [mesh.normals].
Nothing is calculated on
`mesh = Mesh(vertices, faces)` so that if the normals are never used then they are
never calculated.
Accessing the attribute `mesh.normals` initialises and returns
them making `mesh.normals` look like a regular attribute on the outside.
The value is cached so that the calculation never runs more than once.
i.e. `mesh.normals is mesh.normals`.Caches should be reset after a mesh is modified.
Most of this is done automatically.
Mesh modifier methods such as `rotate()`, `translate()` or `crop(in_place=True)`
will all invalidate affected caches themselves.
Similarly, setting any of the `vertices`, `faces` or `vectors` attributes will
reset all caches.
Writing in place to those arrays (e.g. `mesh.vectors[:] = x`) however
is undetectable to `motmot`.
Call `mesh.reset()` after doing an in place modification.[numpy-stl]: https://github.com/wolph/numpy-stl
[@functools.cached_property]: https://docs.python.org/3/library/functools.html#functools.cached_property
[the API reference]: https://motmot.readthedocs.io/en/latest/stubs/mesh.html
[mesh.normals]: https://motmot.readthedocs.io/en/latest/stubs/mesh.html#motmot.Mesh.normals
[KDTree]: https://github.com/storpipfugl/pykdtree
[Polygon meshes]: https://en.wikipedia.org/wiki/Polygon_mesh
[Triangle mesh]: https://en.wikipedia.org/wiki/Triangle_mesh
[STL file]: https://en.wikipedia.org/wiki/STL_(file_format)