import k3d
import numpy as np
# for clear functions
import ddg
import ddg._error_messages as em
from ddg.arrays import (
Curve,
CurveList,
Mesh,
Points,
_default_curve_radius,
_default_curve_sampling,
_default_point_radius,
_default_subspace_size,
_default_surface_sampling,
)
from ddg.math.discrete_objects import triangulated_faces
point_size = 0.1
line_size = 0.01
_conversion_modes = ["FACES", "EDGES", "POINTS"]
def _raise_mode_not_matched():
raise ValueError(f"Mesh mode should be a value in {_conversion_modes}")
def _from_points(points, color, radius=_default_point_radius):
"""Convert points array to k3d.objects.Points
Parameters
----------
points: array_like of shape (_, 3)
The positions of the points in 3D euclidean space.
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
radius: float
Radius of the points
Returns
-------
k3d.objects.Points
"""
points = points.astype(np.float32)
return k3d.points(points, point_size=radius, shader="mesh", color=color)
def _from_curve(curve, color, mode, radius=_default_curve_radius):
"""Convert curve array to instance of k3d.objects.Drawable
Parameters
----------
curve: ddg.arrays.Curve
a curve in R^3
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
mode: str in ["FACES", "EDGES", "POINTS"]
Conversion mode.
radius: float
Radius of the curve
Returns
-------
k3d.objects.Points or k3d.objects.Line
"""
points, periodicity = curve
points = points.astype(np.float32)
match mode:
case "FACES" | "EDGES":
if periodicity:
points = np.vstack((points, points[0]))
return k3d.line(points, width=radius, shader="mesh", color=color)
case "POINTS":
return _from_points(points, color)
case _:
_raise_mode_not_matched()
def _from_mesh(
mesh_array,
color,
mode,
curve_radius=_default_curve_radius,
point_radius=_default_point_radius,
):
"""Convert mesh array to instance of k3d.objects.Drawable
Parameters
----------
mesh_array: ddg.arrays.Mesh
a mesh in R^3
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
mode: str in ["FACES", "EDGES", "POINTS"]
Conversion mode.
curve_radius: float
Radius of the edges (in the case mode="EDGES")
point_radius: float
Radius of the vertices (in the case mode="POINTS")
Returns
-------
k3d.objects.Points, k3d.objects.Mesh or k3d.objects.Lines
"""
points, faces, _ = mesh_array
if len(points) == 0:
# empty object
return None
points = points.astype(np.float32)
faces = faces.astype(np.uint32)
match mode:
case "FACES":
faces = triangulated_faces(faces)
mesh = k3d.mesh(
points, faces, side="double", flat_shading=False, color=color
)
return mesh
case "EDGES":
edges = ddg.arrays.edges(mesh_array).astype(np.float32)
lines = k3d.lines(
points,
edges,
indices_type="segment",
width=curve_radius,
shader="mesh",
color=color,
)
return lines
case "POINTS":
return _from_points(points, color, radius=point_radius)
case _:
_raise_mode_not_matched()
def _to_k3d(
convertible,
color=0x888888,
mode="FACES",
curve_sampling=_default_curve_sampling,
surface_sampling=_default_surface_sampling,
subspace_size=_default_subspace_size,
curve_radius=_default_curve_radius,
point_radius=_default_point_radius,
):
"""Convert pyddg objects, points, curves and meshes to K3D objects.
Parameters
----------
convertible:
The object to convert
mode: str in ["FACES", "EDGES", "POINTS"]
Conversion mode.
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
curve_sampling : tuple of an int and a float
(default=_default_curve_sampling)
Determines the sample number and stepsize for curves
surface_sampling : tuple of an int and a float
(default=_default_surface_sampling)
Determines the sample number and stepsize for surfaces
subspace_size : float
(default=_default_subspace_size)
Determines the size of subspaces
curve_radius : float (default=_default_curve_radius)
Radius for curves, set as `bobj.data.bevel_depth`
point_radius : float (default=_default_point_radius)
Radius of the sphere representing points
Returns
-------
k3d.objects.Group
"""
arrays = ddg.arrays.convert(
convertible,
curve_sampling=curve_sampling,
surface_sampling=surface_sampling,
subspace_size=subspace_size,
)
embedded = ddg.arrays._embed(arrays)
objects = []
match embedded:
case Points(points):
objects.append(_from_points(points, color, radius=point_radius))
case Curve() as curve:
objects.append(_from_curve(curve, color, mode, radius=curve_radius))
case CurveList() as curves:
objects += [
_from_curve(curve, color, mode, radius=curve_radius) for curve in curves
]
case Mesh() as mesh:
obj = _from_mesh(
mesh, color, mode, curve_radius=curve_radius, point_radius=point_radius
)
if obj is not None:
objects.append(obj)
else:
# emtpy object
pass
case _:
raise Exception(em.should_never_happen)
group = k3d.objects.Group(objects)
for obj in group:
obj.color = color
if isinstance(obj, k3d.objects.Line):
obj.width = curve_radius
return group
[docs]def convert(
convertible,
color=0x888888,
curve_sampling=_default_curve_sampling,
surface_sampling=_default_surface_sampling,
subspace_size=_default_subspace_size,
curve_radius=_default_curve_radius,
point_radius=_default_point_radius,
):
"""Convert pyddg objects, points, curves and meshes to K3D objects.
Default conversion mode showing faces.
Parameters
----------
convertible:
The object to convert
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
curve_sampling : tuple of an int and a float
(default=_default_curve_sampling)
Determines the sample number and stepsize for curves
surface_sampling : tuple of an int and a float
(default=_default_surface_sampling)
Determines the sample number and stepsize for surfaces
subspace_size : float
(default=_default_subspace_size)
Determines the size of subspaces
curve_radius : float (default=_default_curve_radius)
Radius for curves, set as `bobj.data.bevel_depth`
point_radius : float (default=_default_point_radius)
Radius of the sphere representing points
Returns
-------
k3d.objects.Group
"""
return _to_k3d(
convertible,
color=color,
mode="FACES",
curve_sampling=curve_sampling,
surface_sampling=surface_sampling,
subspace_size=subspace_size,
curve_radius=curve_radius,
point_radius=point_radius,
)
[docs]def edges(
convertible,
color=0x000000,
radius=_default_curve_radius,
curve_sampling=_default_curve_sampling,
surface_sampling=_default_surface_sampling,
subspace_size=_default_subspace_size,
):
"""Convert pyddg objects, points, curves and meshes to K3D objects.
Special conversion mode showing edges.
Parameters
----------
convertible:
The object to convert
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
radius : float (default=_default_curve_radius)
Sets the radius of the edges as `bobj.data.bevel_depth`
curve_sampling : tuple of an int and a float
(default=_default_curve_sampling)
Determines the sample number and stepsize for curves
surface_sampling : tuple of an int and a float
(default=_default_surface_sampling)
Determines the sample number and stepsize for surfaces
subspace_size : float
(default=_default_subspace_size)
Determines the size of subspaces
Returns
-------
k3d.objects.Group
"""
return _to_k3d(
convertible,
color=color,
mode="EDGES",
curve_sampling=curve_sampling,
surface_sampling=surface_sampling,
subspace_size=subspace_size,
curve_radius=radius,
)
[docs]def vertices(
convertible,
color=0x000000,
radius=_default_point_radius,
curve_sampling=_default_curve_sampling,
surface_sampling=_default_surface_sampling,
subspace_size=_default_subspace_size,
):
"""Convert pyddg objects, points, curves and meshes to K3D objects.
Special conversion mode showing points as spheres.
Parameters
----------
convertible:
The object to convert
color: int, hexadecimal between 0x000000 and 0xffffff
RGB color value as hexadecimal interger
curve_sampling : tuple of an int and a float
(default=_default_curve_sampling)
Determines the sample number and stepsize for curves
surface_sampling : tuple of an int and a float
(default=_default_surface_sampling)
Determines the sample number and stepsize for surfaces
subspace_size : float
(default=_default_subspace_size)
Determines the size of subspaces
Returns
-------
k3d.objects.Group
"""
return _to_k3d(
convertible,
color=color,
mode="POINTS",
curve_sampling=curve_sampling,
surface_sampling=surface_sampling,
subspace_size=subspace_size,
point_radius=radius,
)
[docs]def setup_plot():
"""Setup and return a K3D plot.
Returns
-------
k3d.plot.Plot
"""
return k3d.plot(
grid_visible=False, camera_mode="orbit", camera_auto_fit=False, antialias=5
)
[docs]def clear_plot(plot):
"""Remove objects from plot
Parameters
----------
plot: k3d.plot.Plot
Plot from which to remove the objects from.
"""
while plot.objects:
obj = plot.objects[0]
plot -= obj
[docs]def show_3d(*drawables):
"""Plot a 3D scene containing the drawables.
Parameters
----------
drawables: k3d.objects.Drawable
The objects to plot.
Returns
-------
k3d.plot.Plot
"""
plot = setup_plot()
camera_positon = [0, -8, 3]
camera_target = [0, 0, 0]
camera_up = [0, 0, 1]
plot.camera = camera_positon + camera_target + camera_up
for drawable in drawables:
plot += drawable
return plot
[docs]def show_2d(*drawables):
"""Plot a 2D scene containing the drawables.
Parameters
----------
drawables: k3d.objects.Drawable
The objects to plot.
Returns
-------
k3d.plot.Plot
"""
plot = setup_plot()
plot.camera_fov = 90
plot.lighting = 0
camera_positon = [0, 0, 3]
camera_target = [0, 0, 0]
camera_up = [0, 1, 0]
plot.camera = camera_positon + camera_target + camera_up
for drawable in drawables:
plot += drawable
return plot