Source code for k3d.platonic

from itertools import product
from typing import List as TypingList
from typing import Union

import numpy as np

import k3d


def _wind_outwards(vertices, indices):
    """Return `indices` with every triangle wound counter-clockwise seen from outside.

    These solids are convex and centred on their own centroid, so the test is the sign of the
    face normal against the direction from the centroid to the face.
    """
    v = np.asarray(vertices, np.float64).reshape(-1, 3)
    tris = np.asarray(indices, np.int64).reshape(-1, 3)
    centre = v.mean(axis=0)
    out = []

    for a, b, c in tris:
        normal = np.cross(v[b] - v[a], v[c] - v[a])

        if np.dot(normal, (v[a] + v[b] + v[c]) / 3.0 - centre) < 0.0:
            a, b, c = a, c, b

        out += [int(a), int(b), int(c)]

    return out


class PlatonicSolid:
    """Base class for platonic solids."""

    # quoted: k3d/__init__ imports this module, so k3d.objects is not bound yet when the
    # class body runs. Sphinx and typing.get_type_hints resolve them later, once it is.
    @property
    def mesh(self) -> "k3d.objects.geometry.Mesh":
        """Return k3d.mesh object of the solid."""
        return k3d.mesh(self.vertices, self.indices, side="double")

    @property
    def points(self) -> "k3d.objects.points.Points":
        """Return k3d.points object of solid vertices."""
        return k3d.points(
            self.vertices,
            point_size=np.sqrt(np.sum((self.vertices[0] - self.vertices[1]) ** 2)) / 20,
        )


[docs] class Dodecahedron(PlatonicSolid): """Create a dodecahedron solid.""" def __init__( self, origin: Union[TypingList[float], np.ndarray] = [0, 0, 0], size: float = 1 ) -> None: """Inits Dodecahedron with an origin and a size. Parameters ---------- origin : list, optional The position of centroid of the solid, by default [0, 0, 0]. size : int, optional The size*sqrt(3) is the distance of each vertex of the solid, by default 1. Raises ------ TypeError Origin should have 3 coordinates. """ origin = np.array(origin, dtype=np.float32) if origin.shape == (3,): fi = (1 + np.sqrt(5)) / 2 dodecahedron_vertices = list(product([-1, 1], [-1, 1], [-1, 1])) dodecahedron_vertices += [ (0, fi, 1 / fi), (0, -fi, 1 / fi), (0, -fi, -1 / fi), (0, fi, -1 / fi), (1 / fi, 0, fi), (-1 / fi, 0, fi), (-1 / fi, 0, -fi), (1 / fi, 0, -fi), (fi, 1 / fi, 0), (-fi, 1 / fi, 0), (-fi, -1 / fi, 0), (fi, -1 / fi, 0), ] dodecahedron_vertices = np.float32( size * np.array(dodecahedron_vertices) + origin ) self.vertices = dodecahedron_vertices self.indices = [ 0, 1, 18, 0, 1, 10, 1, 9, 10, 0, 10, 14, 10, 14, 15, 4, 10, 15, 4, 9, 10, 4, 5, 9, 4, 5, 19, 4, 15, 19, 6, 15, 19, 6, 16, 19, 6, 7, 16, 6, 7, 8, 6, 8, 11, 2, 3, 17, 2, 3, 8, 2, 8, 11, 1, 3, 13, 1, 3, 18, 3, 17, 18, 1, 9, 13, 9, 12, 13, 5, 9, 12, 5, 12, 19, 12, 16, 19, 7, 12, 16, 3, 7, 8, 3, 7, 12, 3, 12, 13, 14, 6, 15, 14, 6, 11, 2, 11, 14, 0, 17, 18, 0, 2, 17, 0, 2, 14, ] self.indices = _wind_outwards(self.vertices, self.indices) else: raise TypeError("Origin should have 3 coordinates.")
[docs] class Cube(PlatonicSolid): """Create a cube.""" def __init__( self, origin: Union[TypingList[float], np.ndarray] = [0, 0, 0], size: float = 1 ) -> None: """Inits Cube with an origin and a size. Parameters ---------- origin : list, optional The position of centroid of the solid, by default [0, 0, 0]. size : int, optional The size*sqrt(3) is the distance of each vertex from centroid of the solid, by default 1. Raises ------ TypeError Origin attribute should have 3 coordinates. """ origin = np.array(origin, dtype=np.float32) if origin.shape == (3,): cube_vertices = np.array( list(product([1, -1], [1, -1], [1, -1])), np.float32 ) cube_vertices = np.float32(size * cube_vertices + origin) self.vertices = cube_vertices self.indices = [ 0, 1, 2, 1, 2, 3, 0, 1, 4, 1, 4, 5, 1, 3, 5, 3, 5, 7, 0, 2, 4, 2, 4, 6, 2, 3, 7, 2, 6, 7, 4, 5, 6, 5, 6, 7, ] self.indices = _wind_outwards(self.vertices, self.indices) else: raise TypeError("Origin attribute should have 3 coordinates.")
[docs] class Icosahedron(PlatonicSolid): """Create a icosahedron solid.""" def __init__( self, origin: Union[TypingList[float], np.ndarray] = [0, 0, 0], size: float = 1 ) -> None: """Inits Icosahedron with an origne and a size. Parameters ---------- origin : list, optional The position of centroid of the solid, by default [0, 0, 0]. size : int, optional The size of the solid, by default 1. Raises ------ TypeError Origin attribute should have 3 coordinates. """ origin = np.array(origin, dtype=np.float32) if origin.shape == (3,): fi = (1 + np.sqrt(5)) / 2 icosahedron_vertices = [ (1, fi, 0), (1, -fi, 0), (-1, fi, 0), (-1, -fi, 0), (fi, 0, 1), (fi, 0, -1), (-fi, 0, 1), (-fi, 0, -1), (0, 1, fi), (0, 1, -fi), (0, -1, fi), (0, -1, -fi), ] icosahedron_vertices = np.float32( size * np.array(icosahedron_vertices) + origin ) self.vertices = icosahedron_vertices self.indices = [ 0, 2, 8, 0, 4, 8, 0, 2, 9, 0, 5, 9, 2, 6, 8, 2, 7, 9, 2, 6, 7, 0, 4, 5, 1, 4, 5, 1, 5, 11, 7, 9, 11, 3, 7, 11, 3, 6, 7, 3, 6, 10, 4, 8, 10, 6, 8, 10, 1, 4, 10, 1, 3, 11, 1, 3, 10, 5, 9, 11, ] self.indices = _wind_outwards(self.vertices, self.indices) else: raise TypeError("Origin attribute should have 3 coordinates.")
[docs] class Octahedron(PlatonicSolid): """Create a octahedron solid.""" def __init__( self, origin: Union[TypingList[float], np.ndarray] = [0, 0, 0], size: float = 1 ) -> None: """Inits Octahedron with an origin and a size Parameters ---------- origin : list, optional The position of centroid of the solid, by default [0, 0, 0]. size : int, optional The distance of each vertex from centroid of the solid, by default 1. Raises ------ TypeError Origin attribute should have 3 coordinates. """ origin = np.array(origin, dtype=np.float32) if origin.shape == (3,): octahedron_vertices = [ (1, 0, 0), (0, 1, 0), (0, 0, 1), (-1, 0, 0), (0, -1, 0), (0, 0, -1), ] octahedron_vertices = np.float32( size * np.array(octahedron_vertices) + origin ) self.vertices = octahedron_vertices self.indices = [ 0, 1, 2, 0, 1, 5, 1, 2, 3, 1, 3, 5, 0, 4, 5, 0, 2, 4, 2, 3, 4, 3, 4, 5, ] self.indices = _wind_outwards(self.vertices, self.indices) else: raise TypeError("Origin attribute should have 3 coordinates.")
[docs] class Tetrahedron(PlatonicSolid): """Create a tetrahedron solid.""" def __init__( self, origin: Union[TypingList[float], np.ndarray] = [0, 0, 0], size: float = 1 ) -> None: """Inits Tetrahedron with an origin and a size. Parameters ---------- origin : list, optional The position of centroid of the solid, by default [0, 0, 0]. size : int, optional The size*sqrt(3) is the distance of each vertex from centroid of the solid, by default 1. Raises ------ TypeError Origin attribute should have 3 coordinates. """ origin = np.array(origin, dtype=np.float32) if origin.shape == (3,): tetrahedron_vertices = [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)] tetrahedron_vertices = np.float32( size * np.array(tetrahedron_vertices) + origin ) self.vertices = tetrahedron_vertices self.indices = [0, 1, 2, 0, 1, 3, 1, 2, 3, 0, 2, 3] self.indices = _wind_outwards(self.vertices, self.indices) else: raise TypeError("Origin attribute should have 3 coordinates.")