"""Generate geometric objects as indexed face sets."""
from collections.abc import Iterable as _Iterable
import numpy as np
import ddg.indexedfaceset._ifs as ifs
import ddg.indexedfaceset._ifs_generator as ifs_generator
import ddg.math.discrete_objects as dis_obj
import ddg.math.grids as grids
from ddg.nonexact import get_tol_defaults
[docs]def tetrahedron(co_attr="co"):
"""
Create a tetrahedron as an indexed face set.
By default, standard coordinates of a unit tetrahedron, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A tetrahedron.
"""
obj = ifs.IndexedFaceSet(dis_obj.tetrahedron_faces())
if co_attr:
obj.set_attribute(co_attr, "verts", dis_obj.tetrahedron_coordinates())
return obj
[docs]def cube(co_attr="co"):
"""
Create a cube as an indexed face set
By default, standard coordinates of a unit cube, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A cube.
"""
obj = ifs.IndexedFaceSet(dis_obj.cube_faces())
if co_attr:
obj.set_attribute(co_attr, "verts", dis_obj.cube_coordinates())
return obj
[docs]def octahedron(co_attr="co"):
"""
Create an octahedron as an indexed face set.
By default, standard coordinates of a unit octahedron, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
An octahedron.
"""
obj = ifs.IndexedFaceSet(dis_obj.octahedron_faces())
if co_attr:
obj.set_attribute(co_attr, "verts", dis_obj.octahedron_coordinates())
return obj
[docs]def dodecahedron(co_attr="co"):
"""
Create a dodecahedron as an indexed face set.
By default, standard coordinates of a unit dodecahedron, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A dodecahedron.
"""
obj = ifs.IndexedFaceSet(dis_obj.dodecahedron_faces())
if co_attr:
obj.set_attribute(co_attr, "verts", dis_obj.dodecahedron_coordinates())
return obj
[docs]def icosahedron(co_attr="co"):
"""
Create an icosahedron as an indexed face set.
By default, standard coordinates of a unit icosahedron, centered at
(0, 0, 0), will be assigned to the vertices.
Parameters
----------
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
An icosahedron.
"""
obj = ifs.IndexedFaceSet(dis_obj.icosahedron_faces())
if co_attr:
obj.set_attribute(co_attr, "verts", dis_obj.icosahedron_coordinates())
return obj
[docs]def disc(
resolution=20,
center=(0, 0, 0),
normal=(0, 0, 1),
radius=1,
co_attr="co",
):
"""
Create a disc as an indexed face set.
By default, the disc is centered at (0, 0, 0), the normal is (0, 0, 1)
and the radius is 1.
Parameters
----------
resolution : int (default=20)
The amount of vertices of the disc
center : array_like of shape (3,) (default=(0, 0, 0))
The center of the disc as a list in 3D space.
normal : array_like of shape (3,) (default=(0, 0, 1))
The normal vector of the disc.
radius : float (default=1)
The radius of the disc.
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A disc.
"""
face = dis_obj.disc_face(resolution)
obj = ifs.IndexedFaceSet(face)
if co_attr:
obj.set_attribute(
co_attr,
"verts",
dis_obj.disc_coordinates(resolution, center, normal, radius),
)
return obj
[docs]def cone(
resolution=20,
fill_caps=True,
radius=1,
length=1,
center=(0, 0, 0),
normal=(0, 0, 1),
co_attr="co",
):
"""
Create a cone as an indexed face set.
By default, the cone's base will be at (0, 0, 0) and its tip at
(0, 0, 1).
Parameters
----------
resolution : int (default=20)
The number of vertices of the base.
fill_caps: 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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A cone.
"""
faces = dis_obj.cone_faces(resolution)
if not fill_caps:
faces = faces[1:]
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(
co_attr,
"verts",
dis_obj.cone_coordinates(resolution, radius, length, center, normal),
)
return obj
[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 an indexed face set.
By default, the cylinder's bottom will be at (0, 0, 0) and its top at
(0, 0, 1).
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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A cylinder.
"""
faces = dis_obj.cylinder_faces(resolution)
if not fill_caps:
faces = faces[2:]
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(
co_attr,
"verts",
dis_obj.cylinder_coordinates(
resolution, top_radius, bot_radius, length, center, normal
),
)
return obj
[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 an indexed face set.
This is useful to visualise vectors.
By default, the arrow will point up. The bottom will be at (0, 0, 0) and
the tip at (0, 0, 1).
Parameters
----------
resolution : int (default=20)
The amount of vertices at the base of the tip
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 head and the fourth the height of
the tip.
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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
An arrow.
"""
obj = ifs.IndexedFaceSet(dis_obj.arrow_faces(resolution))
if co_attr:
obj.set_attribute(
co_attr, "verts", dis_obj.arrow_coordinates(resolution, heights, radii)
)
return obj
[docs]def grid(shape, co_attr="co"):
"""
Create a quad grid as an indexed face set.
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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A quad grid.
Examples
--------
>>> import ddg
>>> quad_grid = ddg.indexedfaceset.grid((2, 3))
>>> quad_grid.vertex_attributes["co"]
array([[0, 0],
[1, 0],
[0, 1],
[1, 1],
[0, 2],
[1, 2]])
>>> quad_grid = ddg.indexedfaceset.grid((2, 3, 1))
>>> quad_grid.vertex_attributes["co"]
array([[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 0],
[0, 2, 0],
[1, 2, 0]])
>>> quad_grid = ddg.indexedfaceset.grid((2, 3, 2))
>>> quad_grid.vertex_attributes["co"]
array([[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 0],
[0, 2, 0],
[1, 2, 0],
[0, 0, 1],
[1, 0, 1],
[0, 1, 1],
[1, 1, 1],
[0, 2, 1],
[1, 2, 1]])
"""
faces, coords = grids.quad_grid(shape)
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(co_attr, "verts", coords)
return obj
[docs]def triangle_grid(shape, co_attr="co"):
"""
Create a triangle grid as an indexed face set.
The `shape` defines the number of vertices in each direction.
Currently only supports 2D grids and planar grids in 3D.
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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A triangle grid.
Examples
--------
>>> import ddg
>>> tri_grid = ddg.indexedfaceset.triangle_grid((2, 3))
>>> tri_grid.vertex_attributes["co"]
array([[0, 0],
[2, 0],
[1, 2],
[3, 2],
[2, 4],
[4, 4]])
>>> tri_grid = triangle_grid((2, 3, 1))
>>> tri_grid.vertex_attributes["co"]
array([[0, 0, 0],
[2, 0, 0],
[1, 2, 0],
[3, 2, 0],
[2, 4, 0],
[4, 4, 0]])
"""
faces, coords = grids.triangle_grid(shape)
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(co_attr, "verts", coords)
return obj
def triangulated_quad_grid(shape, co_attr="co"):
"""
Create a triangulated quad grid as an indexed face set.
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 or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A triangulated quad grid.
Examples
--------
>>> from ddg.indexedfaceset import triangulated_quad_grid
>>> tri_grid = triangulated_quad_grid((2, 3))
>>> tri_grid.vertex_attributes["co"]
array([[0, 0],
[1, 0],
[0, 1],
[1, 1],
[0, 2],
[1, 2]])
>>> tri_grid = triangulated_quad_grid((2, 3, 1))
>>> tri_grid.vertex_attributes["co"]
array([[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 0],
[0, 2, 0],
[1, 2, 0]])
>>> tri_grid = triangulated_quad_grid((2, 3, 2))
>>> tri_grid.vertex_attributes["co"]
array([[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 0],
[0, 2, 0],
[1, 2, 0],
[0, 0, 1],
[1, 0, 1],
[0, 1, 1],
[1, 1, 1],
[0, 2, 1],
[1, 2, 1]])
"""
faces, coords = grids.triangulated_quad_grid(shape)
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(co_attr, "verts", coords)
return obj
[docs]def grid_with_periodicity(shape, periodicity=(0, 0), co_attr="co", uv_co_attr="uv"):
"""
Create a quad grid with a periodicity as an indexed face set.
A quad grid with periodicity `(0, 0)` is the box in
Z^2 with lower-left corner `(0, 0)` and upper-right corner
`(shape[0] - 1, shape[1] - 1)`. In other words, the vertices are
`(i_0, i_1)` with `0 <= i_0 <= shape[0] - 1` and `0 <= i_1 <= shape[1] - 1`.
A quad grid with periodicity not equal to `(0, 0)` is obtained from the
quad grid with periodicity `(0, 0)` by gluing the boundary edges as
described in the table below.
Parameters
----------
shape : tuple of length 2
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.
periodicity : tuple of length 2 (default=(0,0))
The periodicity of the grid. This is only meaningful in case of 2D
grid. For 3D shape this attribute doesn't have any effect.
+-------------+---------------+-----------------------------------------------+
| Periodicity | Topology | Gluing Axis |
+=============+===============+===============================================+
| (0, 0) | Disk | None |
+-------------+---------------+-----------------------------------------------+
| (1, 0) | Cylinder | along the first axis |
+-------------+---------------+-----------------------------------------------+
| (0, 1) | Cylinder | along the second axis |
+-------------+---------------+-----------------------------------------------+
| (1, 1) | Torus | along both axes |
+-------------+---------------+-----------------------------------------------+
| (-1, 0) | Moebius Band | along the first axis in reversed orientation |
+-------------+---------------+-----------------------------------------------+
| (0, -1) | Moebius Band | along the second axis in reversed orientation |
+-------------+---------------+-----------------------------------------------+
co_attr : str or None (default="co")
Name of the vertex attribute that stores the coordinates.
If `co_attr=None`, don't assign any coordinates.
uv_co_attr : str or None (default="uv")
Name of the vertex attribute that stores the (i,j) Z^2 coordinates.
If `uv_co_attr=None`, don't assign any coordinates.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
A quad grid.
Examples
--------
>>> import ddg
>>> grid = ddg.indexedfaceset.grid_with_periodicity((2, 3), periodicity=(1, 0))
>>> grid.face_list()
[(0, 1, 3, 2), (2, 3, 5, 4), (0, 1, 3, 2), (2, 3, 5, 4)]
>>> grid.vertex_attributes["co"]
array([[ 1.0000000e+00, 0.0000000e+00, 0.0000000e+00],
[-1.0000000e+00, 0.0000000e+00, 1.2246468e-16],
[ 1.0000000e+00, 1.0000000e+00, 0.0000000e+00],
[-1.0000000e+00, 1.0000000e+00, 1.2246468e-16],
[ 1.0000000e+00, 2.0000000e+00, 0.0000000e+00],
[-1.0000000e+00, 2.0000000e+00, 1.2246468e-16]])
>>> grid.vertex_attributes["uv"]
array([[0, 0],
[1, 0],
[0, 1],
[1, 1],
[0, 2],
[1, 2]])
"""
faces, coords, uv_coords = grids.quad_grid_with_periodicity(shape, periodicity)
obj = ifs.IndexedFaceSet(faces)
if co_attr:
obj.set_attribute(co_attr, "verts", coords)
if uv_co_attr:
obj.set_attribute(uv_co_attr, "verts", uv_coords)
return obj
[docs]def grid_sandwich(shape, uv_attr="uv", co_attr="co", uv_faces_attr="uv"):
"""
Create a rectangle folded in half with overlapping boundaries identified.
Start with a rectangle of shape `(2*m-1, n)`.
Fold the rectangle along the line `(m, *)`.
Identify overlapping boundary vertices and edges.
Topologically the result is a sphere.
For a shape of `(3,3)` one obtains ::
- 7--8--9--8--7
| | | | | |
n 3--4--5--6--3
| | | | | |
- 0--1--2--1--0
|--m--|
|---2*m-1---|
where vertices with equal numbers are identified.
See also
--------
ddg.math.grids.quad_grid_sandwich
Parameters
----------
shape : tuple of length 2
Shape of the folded rectangle viewed from the top.
Should be of the form `(m, n)` with `m >= 2, n >= 3`.
uv_attr : str or None (default="uv")
Name of the vertex attribute that stores the integere uv coordinates.
If `None`, don't set attribute.
co_attr : str or None (default="uv")
Name of the vertex attribute that stores some default coordinates.
If `None`, don't set attribute.
uv_attr : str or None (default="uv")
Name of the face attribute that stores the half-integer uv coordinates.
If `None`, don't set attribute.
Returns
-------
obj : ddg.indexedfaceset.IndexedFaceSet
"""
faces, uv, uv_faces = grids.quad_grid_sandwich(shape)
obj = ifs.IndexedFaceSet(faces)
if uv_attr:
obj.set_attribute(uv_attr, "verts", uv)
if co_attr:
uv_embedded = np.array([np.append(x, 0.0) for x in uv])
obj.set_attribute(co_attr, "verts", uv_embedded)
if uv_faces_attr:
obj.set_attribute(uv_faces_attr, "faces", uv_faces)
return obj
[docs]def sample_domain(
domain, sampling, unbounded_samples=11, attribute_name="uv", atol=None
):
r"""Sample a rectangular domain.
Parameters
----------
domain : list or tuple of length 2 or 3 of list or tuple of length 2 or 3
Domain of the function passed as a nested lists/tuples
representing intervals and their periodicity.
The general form of an interval is (a, b[, p]), where a and b are the lower
and upper bounds of the interval respectively and p is its periodicity with
p = 0 meaning non-periodic, p = 1 periodic orientation preserving and p = -1
periodic orientation reversing.
Note that for 3-dimensional domains periodicity is not supported.
sampling : list or float
| List of sampling options for each direction.
| This should be either a nested list with sampling options for each
| direction (e.g. [[.5, 's'], [.1, 10, 'c']]), a single sampling option
| used for both directions (e.g. [.1, 's']), or a float
| to be used as stepsize for the sampling.
| The following options are supported:
| 's' : stepsize given. The direction of the domain will be sampled with
| the given stepsize. If the direction is unbounded only
| [unbounded_samples] will be taken. This option is also the default, if
| none is explicitely given, e.g. [.2, [.2, 's']] would sample the domain
| in both directions with stepsize .2.
| Format: [stepsize, 's']
| 't' : total number of samples given. The direction of the domain will be
| divided into the given number of unique samples in an equal fashion.
| This option is only available for bounded directions.
| Format: [total_samples, 't']
| 'c' : compound sampling. Acts as a combination of option 's' and 't'. For
| bounded direction this acts like 't', while for unbounded ones
| the stepsize option is used where unbounded_samples is replaced with
| the total amount of samples given.
| Format: [stepsize, total_samples, 'c']
unbounded_samples : int (default = 11)
Amount of samples to take for unbounded directions.
attribute_name : str (default = 'uv')
Grid attribute name to use for the sampled coordinates.
atol : list of 2 floats or float (default = None)
Tolerance to be used during the sampling. If None
is given the global defaults are used. See :py:mod:`~ddg.nonexact`
for more information.
Returns
-------
ddg.indexedfaceset.IndexedFaceSet
Raises
------
ValueError
| if any of the given intervals is not of correct shape,
| or wrong number of arguments is supplied for sampling option,
| or unknown sampling option is given,
| or only 2 samplings are given for a 3-dim. domain,
| or unbounded interval is supposed to be sampled with option 't'
TypeError
if type of argument does not match sampling option, e.g. [.1, 't']
NotImplementedError
if domain is not 2- or 3-dimensional
"""
# Helper for intervals
def bounded(interval):
return interval[0] != -np.inf and interval[1] != np.inf
dim = len(domain)
if dim not in (2, 3):
raise NotImplementedError(
f"Domain has to be 2- or 3-dimensional (is {dim}-dimensional)."
)
# fix tolerances
# use defaults
if atol is None:
atol_ = (get_tol_defaults()["atol"],) * dim
# only one tolerance for both directions
elif len(np.shape(atol)) == 0 or np.shape(atol)[0] == 1:
atol_ = (atol,) * dim
elif len(atol) == dim:
atol_ = atol
else:
raise ValueError(
"Tolerance 'atol' should be passed for each direction or as "
"a default value to use."
)
# Helper to check option validity
def samplingcheck(sampling_):
# dictionaries for expected lengths and types for options
lendict = {"s": 2, "t": 2, "c": 3}
typedict = {"s": ((float, int),), "t": (int,), "c": ((float, int), int)}
if isinstance(sampling_, _Iterable):
opt = sampling_[-1]
if opt in lendict.keys():
if lendict[opt] != len(sampling_):
raise ValueError(
f"Option {opt} takes exactly {lendict[opt] - 1} "
"additional argument(s)."
)
for idx, type_ in enumerate(typedict[opt]):
if not isinstance(sampling_[idx], typedict[opt][idx]):
raise TypeError(
f"Type {type(sampling_[idx])} at position {idx} "
"not supported by option {opt}."
)
else:
raise ValueError(f"Unknown option: {sampling_[-1]}.")
return sampling_
elif isinstance(sampling_, (int, float)):
# stepsize given without 's'
return (sampling_, "s")
# fix sampling
if isinstance(sampling, _Iterable):
if isinstance(sampling[-1], str):
# default sampling given
sampling_ = (samplingcheck(sampling),) * dim
elif len(sampling) == 1:
# default sampling/stepsize
sampling_ = (samplingcheck(sampling[0]),) * dim
elif len(sampling) == dim:
sampling_ = tuple(samplingcheck(i) for i in sampling)
else:
raise ValueError(
"Sampling should be given for each direction or "
"as a default to use for all."
)
else:
# default stepsize given
sampling_ = ((sampling, "s"),) * dim
periodicity = []
dimensions = []
samples = []
for i, interval in enumerate(domain):
match len(interval):
case 2:
periodicity.append(0)
periodic = False
case 3:
periodicity.append(interval[2])
periodic = interval[2] != 0
case _:
raise ValueError(
f"Found strange interval: {interval}. "
"Shape required: (a, b [, periodicity])."
)
# sampling
if bounded(interval):
if sampling_[i][-1] == "s":
famount = (interval[1] - interval[0]) / sampling_[i][0] + 1
amount = np.int_(
np.floor(famount)
) # round and cast to int for linspace
newsample = np.linspace(
interval[0],
interval[0] + (amount - 1) * sampling_[i][0],
num=amount,
)
calcbound = interval[0] + (amount - 1) * sampling_[i][0]
cutsample = (
np.isclose(calcbound, interval[1], atol=atol_[i], rtol=0).any()
& periodic
)
samples.append(newsample[: amount - cutsample])
else:
amount = sampling_[i][0] if sampling_[i][-1] == "t" else sampling_[i][1]
amount += periodic
newsample = np.linspace(interval[0], interval[1], amount)
samples.append(newsample[: amount - periodic])
else:
if sampling_[i][-1] == "t":
raise ValueError(
"Unbounded intervals can only be sampled with"
" options 's[tepsize]' and 'c[ompound]'."
)
unbounded_samples_ = (
sampling_[i][1] if sampling_[i][-1] == "c" else unbounded_samples
)
intervallength = unbounded_samples_ * sampling_[i][0]
if interval[0] > -np.inf:
interval_ = (interval[0], interval[0] + intervallength)
elif interval[1] < np.inf:
interval_ = (
interval[1] - intervallength + sampling_[i][0],
interval[1] + sampling_[i][0],
)
else:
interval_ = (-intervallength / 2, intervallength / 2)
samples.append(np.arange(*interval_, sampling_[i][0]))
dimensions.append(len(samples[i]))
if dim == 2:
periodicity[0], periodicity[1] = periodicity[1], periodicity[0]
grid = ifs_generator.grid_with_periodicity(
dimensions, periodicity=tuple(periodicity)
)
uv = np.array([(i, j) for j in samples[1] for i in samples[0]])
else:
grid = ifs_generator.grid(dimensions)
uv = np.array(
[(i, j, k) for k in samples[2] for j in samples[1] for i in samples[0]]
)
grid.set_attribute(attribute_name, "verts", uv)
return grid