Skip to main content

A sophisticated mesh class for analysing 3D surfaces.

Project description

Motmot

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.

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: Reads and writes a multitude of mesh formats.
    • pymesh: Reads and writes STL and OBJ files.
  • Mesh analysis:

    • 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: 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: 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: 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.
    # 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.

    # `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:

    from motmot import Mesh
    mesh = Mesh("some-file.stl")
    
  4. Read from an lzma, gzip or bzip2 compressed STL file:

    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:

    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.

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.

# 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.

Exact lookup

It is easy to convert vertex IDs to real vertices. Simply pass them as indices to mesh.vertices.

>>> 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.

>>> 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.

    >>> 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 KDTrees 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:

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:

>>> 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:

>>> 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

motmot-0.3.2.tar.gz (37.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

motmot-0.3.2-py3-none-win_amd64.whl (48.1 kB view details)

Uploaded Python 3Windows x86-64

motmot-0.3.2-py3-none-win32.whl (47.6 kB view details)

Uploaded Python 3Windows x86

motmot-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (39.2 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl (39.0 kB view details)

Uploaded Python 3manylinux: glibc 2.5+ x86-64

motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (38.7 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64manylinux: glibc 2.5+ x86-64

motmot-0.3.2-py3-none-manylinux_2_5_i686.manylinux1_i686.whl (38.9 kB view details)

Uploaded Python 3manylinux: glibc 2.5+ i686

motmot-0.3.2-py3-none-macosx_11_0_arm64.whl (36.7 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

motmot-0.3.2-py3-none-macosx_10_6_x86_64.whl (36.7 kB view details)

Uploaded Python 3macOS 10.6+ x86-64

File details

Details for the file motmot-0.3.2.tar.gz.

File metadata

  • Download URL: motmot-0.3.2.tar.gz
  • Upload date:
  • Size: 37.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.4

File hashes

Hashes for motmot-0.3.2.tar.gz
Algorithm Hash digest
SHA256 2363dd96da15739c2cc17882fc641a7b53b0913aaf10285bb2693c8e28999c13
MD5 a184215619246104db58f04a53e8fcef
BLAKE2b-256 e5f1094ebce2f0fb8c7a26f76ff55b101f251e47f15765ced915d9c9889a0e60

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-win_amd64.whl.

File metadata

  • Download URL: motmot-0.3.2-py3-none-win_amd64.whl
  • Upload date:
  • Size: 48.1 kB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.4

File hashes

Hashes for motmot-0.3.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 8e6282135e84c075965109f3ae6c9750764e664ede90e03b4185c9a68e68c239
MD5 aa3803a63552a02b469dae6a6485a837
BLAKE2b-256 97a0537533982be608558038aae9c3e09d8e2bdec5c025fb6bb122848d12fd86

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-win32.whl.

File metadata

  • Download URL: motmot-0.3.2-py3-none-win32.whl
  • Upload date:
  • Size: 47.6 kB
  • Tags: Python 3, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.4

File hashes

Hashes for motmot-0.3.2-py3-none-win32.whl
Algorithm Hash digest
SHA256 da7985e85924b66d7f4f3b403bf58569f3a8339ae7127111bbd1b4becc58eb79
MD5 825b407b56860a2dd71045b1b9849fb6
BLAKE2b-256 b3b7191f7dee59c9031d58ac653fa3ac6c3628524a41d8cf628b295d3e54e9a2

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f6b958ebef0a3f2ed966005ef5c825aa7f42c2581ed0cb3df7fd0a7355baac98
MD5 e8076bf03f75fd9bb6124b88329c70e4
BLAKE2b-256 649849a643bc508b807ce8f126d1564d87b74830946805cecc76dca861263062

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 9b2a0abfd54e9e115b594a182438a1c3f1b8c9dbc38c4352b26e8c72fbce2ecc
MD5 03ea96d4e07217898f5575d0bdbc6bbb
BLAKE2b-256 888620090192a65119568ec1e916160b865f29459f5a280a38d6d8a2c4ee8d9c

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6111cd07bd738a72cf657f5ed7295080b836b26a7f129e6935bb081e0315cc38
MD5 ad6d7e98a0f62c33fa60c4473634ddb7
BLAKE2b-256 53999675ed8de93891d8cd72372f1ae63c85818a78f295b98b3ef6644a95a0ca

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-manylinux_2_5_i686.manylinux1_i686.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-manylinux_2_5_i686.manylinux1_i686.whl
Algorithm Hash digest
SHA256 d5203224e9719213103121e4fe90afd4e77116f90780b4c1fac818f75407a578
MD5 792becde1cb284430c1222cfc3e35ff3
BLAKE2b-256 6c2f29a7807e2560c5770d9e49117f7313056c168b346eb442165d485f984cea

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 44532a05f8eab1f0db591beb7fdfa7a33d8b4c482152b992380bc11a27460ddc
MD5 c349514cad8df079971413e183b5c77b
BLAKE2b-256 cd813afc1b1c7b27986690e50fd71b3c4fa8dd9af5fbe4d6e23f2fed62bc4749

See more details on using hashes here.

File details

Details for the file motmot-0.3.2-py3-none-macosx_10_6_x86_64.whl.

File metadata

File hashes

Hashes for motmot-0.3.2-py3-none-macosx_10_6_x86_64.whl
Algorithm Hash digest
SHA256 c731c65ff235918c2912ae64174cd396e761b3eac9a3628d6ba49a4cc75cad5e
MD5 7f633b73c1028fef7925c02598b1310a
BLAKE2b-256 df907ec66f4f0c7d46172925f8b534e8a170eb00bddd0ac54be2997d59dbce93

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page