# Meshes

A `compas.datastructures.Mesh`

uses a halfedge data structure to represent the topology and geometry of a polygonal mesh,
and to facilitate the application of topological and geometrical operations on it.
In addition, it provides a number of methods for storing arbitrary data on vertices, edges and faces, and the overall mesh itself.

Note

Please refer to the API for a complete overview of all functionality:

## Mesh Construction

Meshes can be constructed in a number of ways:

from scratch, by adding vertices and faces one by one,

using a special constructor function, or

from the data contained in a file.

### From Scratch

```
>>> from compas.datastructures import Mesh
>>> mesh = Mesh()
>>> a = mesh.add_vertex(x=0, y=0, z=0)
>>> b = mesh.add_vertex(x=1, y=0, z=0)
>>> c = mesh.add_vertex(x=1, y=1, z=0)
>>> d = mesh.add_vertex(x=0, y=1, z=0)
>>> face = mesh.add_face([a, b, c, d])
>>> mesh
<Mesh with 4 vertices and 1 faces>
```

### Using Constructors

```
>>> from compas.datastructures import Mesh
>>> mesh = Mesh.from_lines(...)
>>> mesh = Mesh.from_meshgrid(...)
>>> mesh = Mesh.from_polygons(...)
>>> mesh = Mesh.from_polyhedron(...)
>>> mesh = Mesh.from_shape(...)
>>> mesh = Mesh.from_vertices_and_faces(...)
```

### From Data in a File

```
>>> from compas.datastructures import Mesh
>>> mesh = Mesh.from_obj('mesh.obj')
>>> mesh = Mesh.from_off('mesh.off')
>>> mesh = Mesh.from_ply('mesh.ply')
>>> mesh = Mesh.from_stl('mesh.stl')
```

## Visualisation

Like all other COMPAS geometry objects and data structures, meshes can be visualised by placing them in a scene.
For more information about visualisation with `compas.scene.Scene`

, see Visualisation.

```
>>> from compas.datastructures import Mesh
>>> from compas.scene import Scene
>>> mesh = Mesh.from_obj(compas.get('tubemesh.obj'))
>>> scene = Scene()
>>> scene.add(mesh)
>>> scene.show()
```

## Vertices, Edges, Faces

A mesh has vertices, edges, and faces. Vertices are identified by a positive integer that is unique among the vertices of the current mesh. Faces are identified by a positive integer that is unique among the faces of the current mesh. Edges are identified by a pair (tuple) of two vertex identifiers.

```
>>> mesh = Mesh.from_meshgrid(dx=10, dy=10, nx=10, ny=10)
>>> mesh.number_of_vertices()
121
>>> mesh.number_of_edges()
200
>>> mesh.number_of_faces()
81
```

Vertex, edge, and face accessors are generators: they are meant to be used in loops.

```
>>> mesh.vertices()
<generator object Mesh.vertices at ...>
>>> mesh.edges()
<generator object Mesh.edges at ...>
>>> mesh.faces()
<generator object Mesh.faces at ...>
```

```
>>> for vertex in mesh.vertices():
... # do something with this vertex
... print(vertex)
...
0
1
2
...
```

```
>>> for edge in mesh.edges():
... # do something with this edge
... print(edge)
...
(0, 1)
(1, 2)
(2, 3)
...
```

```
>>> for face in mesh.faces():
... # do something with this face
... print(face)
...
0
1
2
...
```

Lists of vertices, edges, and faces have to be constructed explicitly.

```
>>> vertices = list(mesh.vertices())
>>> vertices
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ..., 120]
```

```
>>> edges = list(mesh.edges())
>>> edges
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), ..., (115, 120)]
```

```
>>> faces = list(mesh.faces())
>>> faces
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ..., 80]
```

## Vertex, Edge, Face Attributes

Arbitrary data can be assigned to vertices, edges, and faces, as vertex/edge/face attributes, and to the overall mesh itself. To allow for serialisatin of the mesh and all the data associated with it, the data should be JSON serialisable. See Mesh Serialisation for more information.

The functionality is demonstrated here using vertex attributes. The mechanism is exactly the same for edges and faces.

It is good practice to declare default values for the added data attributes.

```
>>> mesh = Mesh.from_meshgrid(dx=10, dy=10, nx=10, ny=10)
>>> mesh.update_default_vertex_attributes(a=None, b=0.0, c=False)
```

Get the value of one attribute of one vertex.

```
>>> mesh.vertex_attribute(0, 'a')
None
```

Get the value of multiple attributes of one vertex.

```
>>> mesh.vertex_attributes(0, ['a', 'b'])
(None, 0.0)
```

Get the value of one attribute of all vertices.

```
>>> mesh.vertices_attribute('a')
[None, None, None, ... None]
```

Get the value of multiple attributes of all vertices.

```
>>> mesh.vertices_attributes(['b', 'c'])
[(0.0, False), (0.0, False), (0.0, False), ..., (0.0, False)]
```

Similarly, for a selection of vertices.

```
>>> mesh.vertices_attribute('b', vertices=[0, 1, 2, 3])
[0.0, 0.0, 0.0, 0.0]
```

```
>>> mesh.vertices_attributes(['a', 'c'], vertices=[0, 1, 2, 3])
[(None, False), (None, False), (None, False), (None, False)]
```

Updating attributes is currently only possible one vertex at a time.

```
>>> mesh.vertex_attribute(0, 'a', (1.0, 0.0, 0.0))
```

```
>>> for vertex in mesh.vertices():
... if mesh.vertex_degree(vertex) == 2:
... mesh.vertex_attribute(vertex, 'a', (1.0, 0.0, 0.0))
...
```

Finally, note that the xyz coordinates of vertices can be accessed and modified using the same functions.

```
>>> mesh.vertex_attributes(0, 'xyz')
(0.0, 0.0, 0.0)
>>> mesh.vertices_attribute('x')
[0.0, 1.0, 2.0, ..., 9.0]
```

## Halfedge Data Structure

The topology of a mesh is stored in a halfedge data structure. In this data structure, vertices are connected to other vertices, and faces to other faces, via edges. An edge has two connected vertices, and at most two connected faces. Each edge is split into two halfedges, one for each of the connected faces. If an edge has only one connected face, the edge is on the boundary.

Note that in a mesh constructed using `compas.datastructures.Mesh.from_meshgrid()`

, the vertices are organised in a specific way.
We will use that structure to explain some of the topological concepts more easily.

```
>>> mesh = Mesh.from_meshgrid(dx=9, nx=9)
```

```
>>> for i in range(0, 10):
>>> print(mesh.is_vertex_on_boundary(i))
...
True
True
True
True
True
True
True
True
True
True
```

```
>>> for i in range(30, 40):
>>> print(mesh.is_vertex_on_boundary(i))
...
True
False
False
False
False
False
False
False
False
True
```

### Halfedge Cycles

The vertices of a face of the mesh are ordered in a continuous cycle. Every two consecutive vertices are connected by a halfedge of the face. Like the vertices, the halfedges of a face form a continuous cycle. In a valid halfedge mesh, all the cycle directions are consisten. By cycling the faces, each edge is traversed exactly twice, in opposite direction, except fo the edges on the boundary.

```
>>> for face in mesh.faces():
... for edge in mesh.face_halfedges(face):
... print(mesh.halfedge_face(edge) == face)
...
True
True
...
True
```

Using a combination of the halfedge functions, it is possible to traverse the mesh in a number of ways.

### Neighbours

```
>>> for i, nbr in enumerate(mesh.vertex_neighbors(23, ordered=True)):
... print(i, nbr)
...
0 22
1 13
2 24
3 33
```

```
>>> for i, face in enumerate(mesh.vertex_faces(23)):
... print(i, face)
...
0 20
1 11
2 12
3 21
```

```
>>> for i, nbr in enumerate(mesh.face_neighbors(21)):
... print(i, nbr)
...
0 20
1 22
2 23
3 24
```

### Loops and Strips

```
>>> for edge in mesh.halfedge_loop((32, 33)):
... print(edge)
...
(32, 33)
(33, 34)
(34, 35)
(35, 36)
(36, 37)
(37, 38)
(38, 39)
```

```
>>> for edge in mesh.edge_loop((62, 63)):
... print(edge)
...
(60, 61)
(61, 62)
(62, 63)
(63, 64)
(64, 65)
(65, 66)
(66, 67)
(67, 68)
(68, 69)
```

```
>>> for edge in mesh.edge_strip((20, 30)):
... print(edge)
...
(20, 30)
(21, 31)
(22, 32)
(23, 33)
(24, 34)
(25, 35)
(26, 36)
(27, 37)
(28, 38)
(29, 39)
```

## Mesh Geometry

vertex_point

vertex_area

vertex_normal

vertex_laplacian

vertex_curvature

face_area

face_normal

face_flatness

face_circle

face_centroid

face_polygon

edge_vector

edge_line

edge_midpoint

edge_length

edge_direction

## Filtering

vertices_where

edges_where

faces_where

## Mesh Serialisation

```
>>> mesh.to_json('mesh.json')
>>> mesh = Mesh.from_json('mesh.json')
>>> mesh
<Mesh with 121 vertices and 200 faces>
```

```
>>> s = mesh.to_jsonstring()
>>> mesh = Mesh.from_jsonstring(s)
>>> mesh
<Mesh with 121 vertices and 200 faces>
```

```
>>> session = {'mesh': mesh, 'a': 1, 'b': 2}
>>> compas.json_dump(session, 'session.json')
>>> session = compas.json_load('session.json')
>>> mesh = session['mesh']
>>> mesh
<Mesh with 121 vertices and 200 faces>
```

## A Simple Example

mesh from obj

mesh delete faces

mesh remesh

mesh dual

mesh frame subdivision

mesh to FE mesh