Skip to content

Commit

Permalink
volmesh doc draft + example
Browse files Browse the repository at this point in the history
  • Loading branch information
tomvanmele committed Jun 11, 2024
1 parent ed2352f commit 61000c2
Show file tree
Hide file tree
Showing 2 changed files with 308 additions and 6 deletions.
234 changes: 228 additions & 6 deletions docs/userguide/basics.datastructures.volmeshes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Neighbouring cells share at least one face.
A face can be shared by at most two neighbouring cells.
The cycle directions of the faces through which two neighbouring cells are connected are exactly opposite.

Naked faces are faces without an opposite face, or with an opposite face that doesn't belong to a cell.
Naked faces are faces without an opposite face, or with an opposite face that doesn"t belong to a cell.
Cells with naked faces are considered to be on the boundary of the `VolMesh`.

An empty `VolMesh` is valid.
Expand Down Expand Up @@ -101,45 +101,267 @@ Currently only OBJ files containing closed meshes are supported.
* :meth:`VolMesh.from_obj`

>>> from compas.datastructures import VolMesh
>>> volmesh = VolMesh.from_obj("meshes.obj")
>>> volmesh = VolMesh.from_obj("volmeshring.obj")

Equivalently, the geometry of a `VolMesh` can also be written to an OBJ file.
However, note that all additional data attributes (see :ref:`Vertex, Edges, Face, Cell Attributes`) are lost during this process.
Only the geometry survives this conversion process.

>>> volmesh.to_obj("volmeshring.obj")

Visualisation
=============

Like all other COMPAS geometry objects and data structures, volumetric meshes can be visualised by placing them in a scene.
For more information about visualisation with :class:`compas.scene.Scene`, see :doc:`/userguide/basics.visualisation`.
The snippet below uses `meshes.obj`, which is available here: :download:`meshes.obj`.
The snippet below uses `volmesh.obj`, which is available here: :download:`volmeshring.obj`.

>>> from compas.datastructures import VolMesh
>>> from compas.scene import Scene
>>> mesh = Mesh.from_obj("meshes.obj")
>>> mesh = Mesh.from_obj("volmeshring.obj")
>>> scene = Scene()
>>> scene.add(mesh)
>>> scene.show()

.. figure:: /_images/userguide/basics.datastructures.meshes.tubemesh.png

.. figure:: /_images/userguide/basics.datastructures.volmeshes.volmeshring.png

Vertices, Edges, Faces, Cells
=============================

A `VolMesh` can only be constructed by providing information about the vertices and cells.
Edges are created implicitly, and can only exist as part of the topology of a cell.
Faces are not stored explicitly, but are generated automatically based on the corresponding (pairs of) halffaces.

Vertices, faces, and cells are identified by unique, positive and increasing integers.
Edges are identified by pairs of vertex identifiers.

In this section, we will use a `VolMesh` constructed from a mesh grid,
because its highly structured nature allows for easy and transparent counting and verification.

>>> volmesh = VolMesh.from_meshgrid(dx=3, nx=3)

.. figure:: /_images/userguide/basics.datastructures.volmeshes.volmeshgrid_3x3.png

>>> volmesh.number_of_vertices()
64
>>> volmesh.number_of_edges()
144
>>> volmesh.number_of_faces()
108
>>> volmesh.number_of_cells()
27

To iterate over vertices, edges, faces, and cells the `VolMesh` provides corresponding generators.

>>> volmesh.vertices()
<generator object VolMesh.vertices at ...>
>>> volmesh.edges()
<generator object VolMesh.edges at ...>
>>> volmesh.faces()
<generator object VolMesh.faces at ...>
>>> volmesh.cells()
<generator object VolMesh.cells at ...>

These generators are meant to be used in loops.

>>> for vertex in volmesh.vertices():
... print(vertex)
...
0
1
2
3
4
# etc.

>>> for edge in volmesh.edges():
... print(edge)
(0, 1)
(0, 4)
(0, 16)
(1, 2)
(1, 5)
# etc.

The edges are not stored explicitly in the data structure,
but instead generated automatically from the adjacency information of the vertices.
They are generated following the order of the vertex identifiers to make sure the order of edges is deterministic.

>>> for face in volmesh.faces():
... print(face)
0
1
2
3
4
# etc

>>> for cell in volmesh.cells():
... print(cell)
0
1
2
3
4
# etc

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

>>> vertices = list(volmesh.vertices())
>>> vertices
[0, 1, 2, 3, 4, ..., 63]

>>> edges = list(volmesh.edges())
>>> edges
[(0, 1), (0, 4), (0, 16), (1, 2), (1, 5), ..., (62, 63)]

# NOTE: this is not ideal.

>>> faces = list(volmesh.faces())
>>> faces
[???]

>>> cells = list(volmesh.cells())
>>> cells
[0, 1, 2, 3, 4, ..., 27]

Vertex, Edge, Face, Cell Attributes
===================================

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

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

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

>>> volmesh = VolMesh.from_meshgrid(dx=3, nx=10)
>>> volmesh.update_default_vertex_attributes(a=None, b=0.0, c=False)

Get the value of one attribute of one vertex.

>>> volmesh.vertex_attribute(vertex=0, name="a")
None

Get the value of multiple attributes of one vertex.

>>> volmesh.vertex_attributes(vertex=0, names=["a", "b"])
(None, 0.0)

Get the value of one attribute of all vertices.

>>> volmesh.vertices_attribute(name="a")
[None, None, None, ... None]

Get the value of multiple attributes of all vertices.

>>> volmesh.vertices_attributes(names=["b", "c"])
[(0.0, False), (0.0, False), (0.0, False), ..., (0.0, False)]

Similarly, for a selection of vertices.

>>> volmesh.vertices_attribute(name="b", vertices=[0, 1, 2, 3])
[0.0, 0.0, 0.0, 0.0]

>>> volmesh.vertices_attributes(names=["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.

>>> volmesh.vertex_attribute(vertex=0, name="a", value=(1.0, 0.0, 0.0))

>>> for vertex in volmesh.vertices():
... if volmesh.vertex_degree(vertex) == 2:
... volmesh.vertex_attribute(vertex=vertex, name="a", value=(1.0, 0.0, 0.0))
...

Overall Topology
================

More info coming...

* vertex_neighbors
* vertex_cells
* vertex_edges
* vertex_faces
* edge_cells
* edge_faces
* edge_halffaces
* cell_neighbors

Topology of a Cell
==================

Within the context of a cell, the topology of a `VolMesh` is the same as the topology of a regular `Mesh`.
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.

>>> volmesh = VolMesh.from_meshgrid(dx=3, nx=3)
>>> cell = volmesh.cell_sample(size=1).next()
>>>

* cell_vertices
* cell_edges
* cell_halfedges
* cell_halffaces
* cell_faces
* cell_vertex_halffaces
* cell_vertex_faces
* cell_vertex_neighbors
* cell_edge_halffaces
* cell_edge_faces

.. edges and faces are implicit, and only used to store additional data
.. their identifiers are also implicit
.. halfedge_face
.. face_attributes
..
Geometry
========

* vertex_point
* edge_vector
* edge_line
* edge_start
* edge_end
* face_centroid
* face_area
* halfface_centroid
* halfface_polygon
* halfface_normal
* cell_volume
* cell_centroid
* cell_polyhedron

Filtering
=========

* vertices_where
* edges_where
* faces_where
* cells_where

Data Serialisation
==================

>>> volmesh.to_json("volmesh.json")
>>> volmesh = VolMesh.from_json("volmesh.json")

>>> s = volmesh.to_jsonstring()
>>> volmesh = VolMesh.from_jsonstring(s)

>>> session = {"volmesh": volmesh, "a": 1, "b": 2}
>>> compas.json_dump(session, "session.json")
>>> session = compas.json_load("session.json")
>>> volmesh = session["volmesh"]

Examples
========

.. literalinclude:: basics.datastructures.volmeshes_example-1.py
:language: python

80 changes: 80 additions & 0 deletions docs/userguide/basics.datastructures.volmeshes_example-1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from math import radians

import numpy
from compas_viewer import Viewer
from compas_viewer.config import Config
from compas_viewer.scene import BufferGeometry

from compas.colors import Color
from compas.datastructures import VolMesh
from compas.geometry import Box
from compas.geometry import Plane
from compas.tolerance import Tolerance

tolerance = Tolerance()

# =============================================================================
# Base Box
# =============================================================================

box = Box(10).to_mesh()

# =============================================================================
# Cutting Planes
# =============================================================================

planes = [
Plane.worldXY().rotated(radians(30), [0, 1, 0]),
Plane.worldYZ().rotated(radians(30), [0, 0, 1]),
Plane.worldZX().rotated(radians(30), [1, 0, 0]),
Plane.worldXY().translated([0, 0, +2.5]),
Plane.worldXY().translated([0, 0, -2.5]),
]

# =============================================================================
# Cuts
# =============================================================================

results = [box]
for plane in planes:
temp = []
for box in results:
result = box.slice(plane)
if result:
temp += result
else:
temp.append(box)
results = temp

# =============================================================================
# VolMesh Construction
# =============================================================================

volmesh = VolMesh.from_meshes(results)

volmesh.translate([5, 5, 5])

# =============================================================================
# Visualisation
# =============================================================================

config = Config()
config.camera.target = [5, 5, 5]
config.camera.position = [-8, -15, 10]

viewer = Viewer(config=config)

wires = numpy.asarray([volmesh.edge_coordinates(edge) for edge in volmesh.edges()])
viewer.scene.add(BufferGeometry(lines=wires), name="Wires")

cell = list(volmesh.cell_sample(size=1))[0]
cell_color = {cell: Color.red()}
for nbr in volmesh.cell_neighbors(cell):
cell_color[nbr] = Color.green()

for cell in cell_color:
color = cell_color[cell]
mesh = volmesh.cell_to_mesh(cell)
viewer.scene.add(mesh, facecolor=color)

viewer.show()

0 comments on commit 61000c2

Please sign in to comment.