"""Module '_surface_generator' defines some primitive halfedge surfaces"""
import numpy as np
from scipy.spatial import ConvexHull
import ddg
import ddg.halfedge._modify as modify
import ddg.indexedfaceset._ifs_generator as ifs_gen
from ddg.math.euclidean import rotation_from_to
#######
# grids
#######
def _convert_to_halfedge(ifs_obj):
"""
Convert an indexed face set to a surface.
This is a helper function.
Parameters
----------
ifs_obj : ddg.datastructures.indexedfaceset.ifs.IndexedFaceSet
The indexed face set that should be converted.
Returns
-------
ddg.datastructures.halfedge.surface.Surface
The converted indexed face set as a surface.
"""
from ddg.indexedfaceset._utils import indexed_face_set_to_surface
return indexed_face_set_to_surface(ifs_obj, None, None)
def _add_vertex_coordinates(ifs_obj, s, co_attr):
"""
Add vertex coordinates from `ifs_obj` to `s`.
This is a helper function that adds the coordinates of `ifs_obj` to `s`.
Note that the coordinates get added to `s` in-place and nothing is returned.
Parameters
----------
ifs_obj : ddg.datastructures.indexedfaceset.ifs.IndexedFaceSet
The equivalent of `s` as an indexed face set.
s : ddg.datastructures.halfedge.surface.Surface
The surface to which the coordinates should be added to.
co_attr : str
The name of the vertex attribute that stores the coordinates.
"""
coords = ifs_obj.vertex_attributes[co_attr]
s.verts.add_attribute(co_attr)
for v, co in zip(s.verts, coords, strict=True):
getattr(s.verts, co_attr)[v] = co
[docs]def grid(shape, co_attr="co"):
"""
Create a triangle grid as a half-edge surface.
The `shape` defines the number of vertices in each direction.
Both 2D and 3D grids are supported.
Parameters
----------
shape : tuple of length 2 or 3
Shape of the grid. The first entry is the number of vertices in the
x-direction, the second the number of vertices in the y-direction
and the third (if given) the number of vertices in the z-direction.
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A quadrilateral grid.
"""
ifs_obj = ifs_gen.grid(shape, co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def triangle_grid(shape, co_attr="co"):
"""
Create a triangle grid as a halfedge.
The `shape` defines the number of vertices in each direction.
Both 2D and 3D grids are supported.
Parameters
----------
shape : tuple of length 2 or 3
Shape of the grid. The first entry is the number of vertices in the
x-direction, the second the number of vertices in the y-direction
and the third (if given) the number of vertices in the z-direction.
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A triangle grid.
"""
ifs_obj = ifs_gen.triangle_grid(shape, co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
#################
# platonic solids
#################
[docs]def tetrahedron(co_attr="co"):
"""
Create a tetrahedron as a half-edge surface.
By default, standard coordinates of a unit tetrahedron, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A tetrahedron.
"""
ifs_obj = ifs_gen.tetrahedron(co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def cube(co_attr="co"):
"""
Create a cube as a half-edge surface.
By default, standard coordinates of a unit cube, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A cube.
"""
ifs_obj = ifs_gen.cube(co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def octahedron(co_attr="co"):
"""
Create an octahedron as a half-edge surface.
By default, standard coordinates of a unit octahedron, centered at [0,0,0],
will be assigned to the vertices and can be accessed via the attribute
coordinate `co`.
Parameters
----------
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
An octahedron.
"""
ifs_obj = ifs_gen.octahedron(co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def dodecahedron(co_attr="co"):
"""
Create a dodecahedron as a half-edge surface.
By default, standard coordinates of a unit dodecahedron, centered at
[0, 0, 0], will be assigned to the vertices and can be accessed via the
attribute coordinate `co`.
Parameters
----------
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A dodecahedron.
"""
ifs_obj = ifs_gen.dodecahedron(co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def icosahedron(co_attr="co"):
"""
Create an icosahedron as a half-edge surface.
By default, standard coordinates of a unit icosahedron, centered at
[0, 0, 0], will be assigned to the vertices and can be accessed via the
attribute coordinate `co`.
Parameters
----------
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
An icosahedron.
"""
ifs_obj = ifs_gen.icosahedron(co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def icosphere(subdivision_steps=1, radius=1, co_attr="co"):
"""
Create an icosphere as a half-edge surface.
By default, standard coordinates of a unit icosphere, centered at
[0, 0, 0], will be assigned to the vertices and can be accessed via
the attribute coordinate `co`.
Parameters
----------
subdivision_steps : int (default=1)
Number of subdivisions performed, e.g. 0 for the icosahedron, 1 to
subdivide each face of the icosahedron into 4 triangles, 2 to subdivide
each face of the icosahedron into 16 triangles and so on.
radius : float (default=1)
radius of the icosphere
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
An icosphere.
Notes
-----
* Coordinate attribute
The coordinate attribute `co_attr` assigned to the vertices is of type
`numpy.ndarray` and contains the standard coordinates of a unit icosphere.
"""
icosphere = icosahedron()
radius = radius
if co_attr:
modify.subdivide(icosphere, subdivision_steps, co_attr)
else:
modify.subdivide(icosphere, subdivision_steps, None)
for v in icosphere.verts:
v.co *= radius / np.linalg.norm(v.co)
return icosphere
[docs]def convexhull_3d(A, join_coplanar_triangles=True, co_attr="co"):
"""
Creates a halfedge surface of the convex hull of the input points.
Parameters
----------
A : array
Input array of 3 dimensional coordinates of the form [ , , ]
Dimension of the input points has to be three, i.e. non-planar.
join_coplanar_triangles : bool (default=True)
If `True`, planar faces will stay triangulated.
Otherwise the triangulation will be removed and result in one bigger face.
co_attr : str (default="co")
The name of the vertex attribute that stores the coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
The convex hull.
Notes
-----
* Coordinate attribute
The coordinate attribute `co_attr` assigned to the vertices is of type
`numpy.ndarray` and contains the coordinates of the input.
"""
hull = ConvexHull(A)
s = ddg.indexedfaceset.indexed_face_set_to_surface(
ddg.indexedfaceset.GeneralizedIndexedFaceSet(hull.simplices)
)
index_list = list(v.ifs_index for v in s.verts)
# generating coordinates
s.verts.add_attribute(co_attr)
for f in s.faces:
for e in ddg.halfedge.edge_loop(f.edge):
i = e.head.ifs_index
if i in index_list:
e.head.co = np.array(A[i])
index_list.remove(i)
# Check for lin independency and joins faces if needed
if join_coplanar_triangles:
modify.join_coplanar_faces(s)
return s
[docs]def disc(
circle_subdivisions=20,
fill_face=True,
radius=1,
center=(0, 0, 0),
normal=(0, 0, 1),
co_attr="co",
):
"""
Create a disc as a half-edge surface.
By default, the disc is embedded in R^3 with center (0, 0, 0), radius 1 and
normal vector (0, 0, 1).
Parameters
----------
circle_subdivisions: int (default=20)
Number of vertices on the boundary of the circle or disc.
fill_face: bool (default=True)
If `True`, fill the edge loop with a face.
radius : float (default=1)
The radius of the disc.
center : array_like of shape (3,) (default=(0, 0, 0))
The center of the disc as an array_like in 3D space.
normal : array_like of shape (3,) (default=(0, 0, 1))
The normal vector of the disc.
co_attr : str (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A disc.
"""
ifs_obj = ifs_gen.disc(circle_subdivisions, center, normal, radius, co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
if not fill_face:
modify.remove_face(ddg.halfedge.some_face(s))
return s
def cone(
resolution=20,
fill_face=True,
radius=1,
length=1,
center=(0, 0, 0),
normal=(0, 0, 1),
co_attr="co",
):
"""
Create a cone as a half-edge surface.
Parameters
----------
resolution: int (default=20)
The number of vertices of the base.
fill_face : bool (default=True)
If `True`, include the face of the base.
radius : float (default=1)
The radius of the base of the cone.
length : float (default=1)
The length of the cone.
center : array_like of shape (3,) (default=(0, 0, 0))
The center of the base of the cone.
normal : array_like of shape (3,) (default=(0, 0, 1))
The normal of the cone's base face.
co_attr : str, optional (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A cone.
"""
ifs_obj = ifs_gen.cone(
resolution, fill_face, radius, length, center, normal, co_attr
)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
[docs]def cylinder(
resolution=20,
fill_caps=True,
top_radius=1,
bot_radius=1,
length=1,
center=(0, 0, 0),
normal=(0, 0, 1),
co_attr="co",
):
"""
Create a cylinder as a half-edge surface.
Parameters
----------
resolution: int (default=20)
The number of vertices on each side.
fill_caps : bool (default=True)
If `True`, include the faces of the bottom and the top.
top_radius : float (default=1)
The radius of the top of the cylinder.
bot_radius : float (default=1)
The radius of the bottom of the cylinder
length : float (default=1)
The length of the cylinder.
center : array_like of shape (3,) (default=(0, 0, 0))
The center of the bottom of the cylinder.
normal : array_like of shape (3,) (default=(0, 0, 1))
The normal of the cylinder's top and bottom faces.
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A cylinder.
"""
ifs_obj = ifs_gen.cylinder(
resolution,
fill_caps,
top_radius,
bot_radius,
length,
center,
normal,
co_attr,
)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s
def _cylinder_along_y_axis(
resolution=20,
fill_caps=True,
top_radius=1,
bot_radius=1,
length=1,
center=(0, 0, 0),
normal=(0, 0, 1),
co_attr="co",
):
"""
Create a cylinder as a half-edge surface with axis being the y-axis.
This is a legacy function that should not be used.
Parameters
----------
resolution: int (default=20)
The number of vertices on each side.
fill_caps : bool (default=True)
If `True`, include the faces of the bottom and the top.
top_radius : float (default=1)
The radius of the top of the cylinder.
bot_radius : float (default=1)
The radius of the bottom of the cylinder
length : float (default=1)
The length of the cylinder.
center : array_like of shape (3,) (default=(0, 0, 0))
The center of the bottom of the cylinder.
normal : array_like of shape (3,) (default=(0, 0, 1))
The normal of the cylinder's top and bottom faces.
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
A cylinder.
"""
s = cylinder(
resolution, fill_caps, top_radius, bot_radius, length, center, normal, co_attr
)
F = rotation_from_to((0, 0, 1), (0, 1, 0), homogeneous=False)
for v in s.verts:
v.co = F @ v.co
return s
[docs]def arrow(
resolution=20,
heights=(0, 0.7, 0.7, 1),
radii=(0.05, 0.05, 0.125),
co_attr="co",
):
"""
Create an arrow as a half-edge surface.
This is useful to visualise vectors.
By default, the arrow will point up. The bottom will be at (0, 0, 0) and
the head at (0, 0, 1).
Parameters
----------
resolution : int (default=20)
Number of vertices around the center of rotation.
Minimum is 3.
heights : float sequence of length 4 (default=(0, 0.7, 0.7, 1))
First element defines height of the bottom of the stick, the
second the height of the top of the stick, the third the
height of the base of the tip and the forth the height of
the tip of the head.
radii : float sequence of length 3 (default=(0.05, 0.05, 0.125))
First element defines the radius of the bottom of the stick,
second the radius of the top of the stick and the third the
radius of the base of the tip.
co_attr : str, optional (default="co")
The name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
s : ddg.datastructures.halfedge.surface.Surface
An arrow.
"""
ifs_obj = ifs_gen.arrow(resolution, heights, radii, co_attr)
s = _convert_to_halfedge(ifs_obj)
if co_attr:
_add_vertex_coordinates(ifs_obj, s, co_attr)
return s