Source code for k3d.factory.geometry

"""Factory functions for geometric objects."""

import warnings
from typing import Any, Callable, Optional, Tuple, Union
from typing import Dict as TypingDict
from typing import List as TypingList

import numpy as np

from ..helpers import check_attribute_color_range, image_format, pack_colors
from ..objects import STL, Line, Lines, Mesh, Surface
from ..objects.base import has_data
from ..transform import process_transform_arguments
from .common import _default_color, default_colormap, factory_color

# only LineMesh builds a lit material; thick and simple draw unlit tubes and lines, so these
# two traits reach the browser and nothing reads them
LINE_MATERIAL_DEFAULTS = {"roughness": 0.4, "metalness": 0.0}


def _warn_unlit_line(shader, roughness, metalness):
    if shader == "mesh":
        return

    ignored = [name for name, value in (("roughness", roughness), ("metalness", metalness))
               if value is not None and value != LINE_MATERIAL_DEFAULTS[name]]

    if ignored:
        warnings.warn(
            "%s %s ignored by the '%s' line shader, which draws unlit geometry - use "
            "shader='mesh' for a lit line" % (
                " and ".join(ignored), "are" if len(ignored) > 1 else "is", shader),
            stacklevel=3,
        )


# Type aliases for better readability
ArrayLike = Union[TypingList, np.ndarray, Tuple]
ColorMap = Union[TypingList[TypingList[float]], TypingDict[str, Any], np.ndarray]
ColorRange = TypingList[float]


[docs] def lines( vertices: ArrayLike, indices: ArrayLike, indices_type: str = "triangle", color: int = _default_color, colors: TypingList[int] = None, # lgtm [py/similar-function] attribute: ArrayLike = None, color_map: Optional[ColorMap] = None, color_range: ColorRange = None, width: float = 0.01, shader: str = "thick", roughness: float = 0.4, metalness: float = 0.0, shininess: float = None, radial_segments: int = 8, opacity: float = 1.0, name: Optional[str] = None, group: Optional[str] = None, custom_data: Optional[TypingDict[str, Any]] = None, compression_level: int = 0, visible: bool = True, **kwargs: Any, ) -> Lines: """ Create a Line drawable for plotting segments and polylines. Parameters ---------- vertices : array_like Array with (x, y, z) coordinates of segment endpoints. indices : array_like Array of vertex indices: int pair or triple of indices from vertices array. indices_type : {'segment', 'triangle'}, optional Interpretation of indices array. 'segment' for pairs, 'triangle' for triples. Default is 'triangle'. color : int, optional Packed RGB color of the lines (0xff0000 is red, 0xff is blue) when `colors` is empty. Default is _default_color. colors : array_like, optional Array of int: packed RGB colors (0xff0000 is red, 0xff is blue) when attribute, color_map and color_range are empty. Default is []. attribute : array_like, optional Array of float attribute for the color mapping, corresponding to each vertex. Default is []. color_map : list, optional A list of float quadruplets (attribute value, R, G, B), sorted by attribute value. The first quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. Default is default_colormap. color_range : list, optional A pair [min_value, max_value], which determines the levels of color attribute mapped to 0 and 1 in the color map respectively. Default is []. width : float, optional Thickness of the lines. Default is 0.01. shader : {'simple', 'thick', 'mesh'}, optional Display style (name of the shader used) of the lines. Default is 'thick'. roughness : float, optional Roughness of object material. Default is 0.4. metalness : float, optional Metalness of object material. Default is 0.0. radial_segments : int, optional Number of segmented faces around the circumference of the tube. Default is 8. opacity : float, optional Opacity of lines. Default is 1.0. name : str, optional A name of the object. Default is None. group : str, optional A name of a group. Default is None. custom_data : dict, optional An object with custom data attached to object. Default is None. compression_level : int, optional Level of compression [-1, 9]. Default is 0. visible : bool, optional Whether the object is drawn. Default is True. **kwargs Additional keyword arguments passed to process_transform_arguments. Returns ------- Lines The created Lines drawable object. """ if colors is None: colors = [] if attribute is None: attribute = [] if color_range is None: color_range = [] if color_map is None: color_map = default_colormap color_map = ( np.array(color_map, np.float32) if type(color_map) is not dict else color_map ) attribute = ( np.array(attribute, np.float32) if type(attribute) is not dict else attribute ) color_range = check_attribute_color_range(attribute, color_range) _warn_unlit_line(shader, roughness, metalness) return process_transform_arguments( Lines( vertices=vertices, indices=indices, indices_type=indices_type, color=color, width=width, shader=shader, roughness=roughness, metalness=metalness, shininess=shininess, radial_segments=radial_segments, colors=colors, attribute=attribute, color_map=color_map, color_range=color_range, opacity=opacity, name=name, group=group, custom_data=custom_data, compression_level=compression_level, visible=visible, ), **kwargs, )
[docs] def line( vertices: ArrayLike, color: int = _default_color, colors: TypingList[int] = None, # lgtm [py/similar-function] attribute: ArrayLike = None, color_map: Optional[ColorMap] = None, color_range: ColorRange = None, width: float = 0.01, opacity: float = 1.0, shader: str = "thick", roughness: float = 0.4, metalness: float = 0.0, shininess: float = None, radial_segments: int = 8, name: Optional[str] = None, group: Optional[str] = None, custom_data: Optional[TypingDict[str, Any]] = None, compression_level: int = 0, visible: bool = True, **kwargs: Any, ) -> Line: """ Create a Line drawable for plotting segments and polylines. Parameters ---------- vertices : array_like Array with (x, y, z) coordinates of segment endpoints. color : int, optional Hex color of the lines when `colors` is empty, by default _default_color. colors : list, optional Array of Hex colors when attribute, color_map and color_range are empty, by default []. attribute : list, optional List of values used to apply `color_map`, by default []. color_map : list, optional List of `float` quadruplets (attribute value, R, G, B) sorted by attribute value, by default None. The first quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. color_range : list, optional [min_value, max_value] pair determining the levels of color attribute mapped to 0 and 1 in the colormap, by default []. width : float, optional Thickness of the lines, by default 0.01. shader : {'simple', 'thick', 'mesh'}, optional Display style of the lines, by default 'thick'. roughness : float, optional Roughness of object material, by default 0.4. metalness : float, optional Metalness of object material, by default 0.0. radial_segments : int, optional Number of segmented faces around the circumference of the tube, by default 8. name : str, optional Object name, by default None. group : str, optional Name of a group, by default None. custom_data : dict, optional An object with custom data attached to object, by default None. compression_level : int, optional Level of data compression [-1, 9], by default 0. visible : bool, optional Whether the object is drawn. Default is True. **kwargs Additional keyword arguments passed to process_transform_arguments. Returns ------- Line The created Line drawable object. """ if colors is None: colors = [] if attribute is None: attribute = [] if color_range is None: color_range = [] if color_map is None: color_map = default_colormap color_map = ( np.array(color_map, np.float32) if type(color_map) is not dict else color_map ) attribute = ( np.array(attribute, np.float32) if type(attribute) is not dict else attribute ) color_range = check_attribute_color_range(attribute, color_range) _warn_unlit_line(shader, roughness, metalness) return process_transform_arguments( Line( vertices=vertices, color=color, width=width, shader=shader, radial_segments=radial_segments, colors=colors, attribute=attribute, color_map=color_map, color_range=color_range, opacity=opacity, roughness=roughness, metalness=metalness, shininess=shininess, name=name, group=group, custom_data=custom_data, compression_level=compression_level, visible=visible, ), **kwargs, )
[docs] def mesh( vertices: ArrayLike, indices: ArrayLike, normals: ArrayLike = None, color: Optional[int] = None, colors: TypingList[int] = None, opacities: ArrayLike = None, attribute: ArrayLike = None, color_map: Optional[ColorMap] = None, # lgtm [py/similar-function] color_range: ColorRange = None, wireframe: bool = False, flat_shading: bool = True, roughness: float = 0.4, metalness: float = 0.0, shininess: float = None, opacity: float = 1.0, texture: Optional[bytes] = None, texture_file_format: Optional[str] = None, volume: ArrayLike = None, volume_bounds: ArrayLike = None, opacity_function: ArrayLike = None, side: str = "front", uvs: Optional[ArrayLike] = None, uvs2: Optional[ArrayLike] = None, texture_wrap: str = "clamp", emissive: int = 0, emissive_intensity: float = 1.0, emissive_map: Optional[bytes] = None, normal_map: Optional[bytes] = None, normal_scale: float = 1.0, metalness_roughness_map: Optional[bytes] = None, occlusion_map: Optional[bytes] = None, occlusion_strength: float = 1.0, alpha_mode: Optional[str] = None, alpha_cutoff: float = 0.5, transmission: float = 0.0, ior: float = 1.5, thickness: float = 0.0, attenuation_color: int = 0xFFFFFF, attenuation_distance: float = 0.0, slice_planes: ArrayLike = None, name: Optional[str] = None, group: Optional[str] = None, custom_data: Optional[TypingDict[str, Any]] = None, compression_level: int = 0, triangles_attribute: ArrayLike = None, visible: bool = True, click_callback: Optional[Callable] = None, hover_callback: Optional[Callable] = None, **kwargs: Any, ) -> Mesh: """Create a Mesh drawable from 3D triangles. Parameters ---------- vertices : array_like Array of triangle vertices, `float` (x, y, z) coordinate triplets. indices : array_like Array of vertex indices. `int` triplets of indices from vertices array. normals: array_like, optional Array of vertex normals: float (x, y, z) coordinate triples. Normals are used when flat_shading is false. If the normals are not specified here, normals will be automatically computed. color : int, optional Hex color of the mesh. It multiplies `colors`, the colormap and `texture`, the way a base colour does in other renderers. By default _default_color, or white when any of them is given. .. versionchanged:: 3.2.0 `color` used to be ignored when `colors`, a colormap or a texture was given. colors : array_like, optional Colors per vertex: packed hex ints, or an (N, 3) or (N, 4) array of RGB(A) - floats in 0..1 or integers in 0..255. The fourth column becomes `opacities`. By default []. opacities : array_like, optional Alpha per vertex, multiplied into the colour; read when `alpha_mode` is 'blend' or 'mask', by default []. attribute: list, optional List of values used to apply `color_map`, by default []. color_map : list, optional List of `float` quadruplets (attribute value, R, G, B) sorted by attribute value, by default None. The first quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. color_range : list, optional [min_value, max_value] pair determining the levels of color attribute mapped to 0 and 1 in the colormap, by default []. wireframe : bool, optional Display the mesh as wireframe, by default False. flat_shading : bool, optional Display the mesh with flat shading, by default True. roughness: `float`. Roughness of object material. metalness: `float`. Metalness of object material. opacity : float, optional Opacity of the mesh, by default 1.0. texture : bytes, optional Image data in a specific format, by default None. texture_file_format : str, optional Format of the data, by default None - read from the data itself. It should be the second part of MIME format of type 'image/',e.g. 'jpeg', 'png', 'gif', 'tiff'. volume : list, optional 3D array of `float`, by default []. volume_bounds : list, optional 6-element tuple specifying the bounds of the volume data (x0, x1, y0, y1, z0, z1), by default []. opacity_function : list, optional `float` tuples (attribute value, opacity) sorted by attribute value, by default []. The first tuples should have value 0.0, the last 1.0; opacity is in the range 0.0 to 1.0. side : {'front', 'back', 'double'}, optional Side to render, by default "front". uvs : array_like, optional float uvs for the texturing corresponding to each vertex, by default None. uvs2 : array_like, optional A second set of uvs, read by `occlusion_map` alone; it falls back to `uvs`, by default None. texture_wrap : str, optional What every texture of the mesh does outside 0..1: 'clamp', 'repeat' or 'mirror', or one for u and one for v, e.g. 'repeat clamp'. By default 'clamp'. The cinematic renderer repeats every texture. emissive : int, optional Hex color the surface emits, unaffected by lighting, by default 0 (none). emissive_intensity : float, optional Multiplier of `emissive`, by default 1.0. emissive_map : bytes, optional Image (PNG, JPEG, WebP, GIF) multiplying `emissive`, by default None. normal_map : bytes, optional Tangent-space normal map image, read with `uvs`, by default None. normal_scale : float, optional Strength of `normal_map`, by default 1.0. metalness_roughness_map : bytes, optional Image whose green channel multiplies `roughness` and blue channel `metalness` - the glTF layout, by default None. occlusion_map : bytes, optional Image whose red channel darkens the indirect light, read with `uvs2` when given, by default None. occlusion_strength : float, optional How much of `occlusion_map` applies, 0 to 1, by default 1.0. alpha_mode : {'opaque', 'blend', 'mask'}, optional How the alpha of `texture` and `opacities` is used: ignored ('opaque'), blended with what is behind ('blend'), or cut out below `alpha_cutoff` ('mask'). `opacity` fades the mesh in every mode. By default 'blend' when `opacities` are given, 'opaque' otherwise. alpha_cutoff : float, optional Threshold of the 'mask' mode, by default 0.5. transmission : float, optional How much light passes through the surface, refracted - glass, water, gems - from 0 to 1, by default 0. ior : float, optional Index of refraction of a transmissive mesh, by default 1.5. thickness : float, optional Thickness of the volume behind a transmissive surface, by default 0 (a thin wall). attenuation_color : int, optional Hex colour light takes on after `attenuation_distance` in the volume, by default white. attenuation_distance : float, optional Distance after which light has the `attenuation_color`, by default 0 (no attenuation). name : str, optional Object name, by default None. group : str, optional Name of a group, by default None. custom_data: `dict` A object with custom data attached to object. compression_level : int, optional Level of data compression [-1, 9], by default 0. triangles_attribute : list, optional Array of float attribute for the color mapping, one value per triangle rather than per vertex; used when `attribute` is empty, by default []. slice_planes : list, optional Planes [a, b, c, d] the section outline is drawn along, up to eight of them. The outline is drawn in the object colour, without the colormap, by default []. visible : bool, optional Whether the object is drawn. Default is True. click_callback : callable, optional Called with the picking parameters when the object is clicked, while the plot is in mode='callback'. Default is None. hover_callback : callable, optional Called with the picking parameters when the cursor is over the object, while the plot is in mode='callback'. Default is None. **kwargs For other keyword-only arguments, see :ref:`process_transform_arguments`. Returns ------- Mesh Mesh Drawable """ # Ensure arraylike attributes are initialized as [] if None if colors is None: colors = [] if slice_planes is None: slice_planes = [] if normals is None: normals = [] if attribute is None: attribute = [] if triangles_attribute is None: triangles_attribute = [] if volume is None: volume = [] if volume_bounds is None: volume_bounds = [] if opacity_function is None: opacity_function = [] if uvs is None: uvs = [] if uvs2 is None: uvs2 = [] if opacities is None: opacities = [] colors, alpha = pack_colors(colors) if alpha is not None: if has_data(opacities): raise ValueError("opacities given twice: as the fourth column of colors and as opacities") opacities = alpha if alpha_mode is None: alpha_mode = "blend" if has_data(opacities) else "opaque" if texture is not None and texture_file_format is None: texture_file_format = image_format(texture) color = factory_color(color, colors, attribute, triangles_attribute, texture) if color_map is None: color_map = default_colormap color_map = ( np.array(color_map, np.float32) if type(color_map) is not dict else color_map ) uvs = np.array(uvs, np.float32) if type(uvs) is not dict else uvs uvs2 = np.array(uvs2, np.float32) if type(uvs2) is not dict else uvs2 opacities = np.array(opacities, np.float32) if type(opacities) is not dict else opacities attribute = ( np.array(attribute, np.float32) if type(attribute) is not dict else attribute ) normals = np.array(normals, np.float32) if type(normals) is not dict else normals triangles_attribute = ( np.array(triangles_attribute, np.float32) if type(triangles_attribute) is not dict else triangles_attribute ) volume_bounds = ( np.array(volume_bounds, np.float32) if type(volume_bounds) is not dict else volume_bounds ) if len(attribute) > 0: color_range = check_attribute_color_range(attribute, color_range) if len(triangles_attribute) > 0: color_range = check_attribute_color_range(triangles_attribute, color_range) if len(volume) > 0: color_range = check_attribute_color_range(volume, color_range) return process_transform_arguments( Mesh( vertices=vertices, indices=indices, normals=normals, color=color, colors=colors, attribute=attribute, triangles_attribute=triangles_attribute, color_map=color_map, color_range=color_range, wireframe=wireframe, flat_shading=flat_shading, roughness=roughness, metalness=metalness, shininess=shininess, opacity=opacity, volume=volume, volume_bounds=volume_bounds, opacity_function=opacity_function, side=side, texture=texture, uvs=uvs, uvs2=uvs2, texture_file_format=texture_file_format, texture_wrap=texture_wrap, opacities=opacities, emissive=emissive, emissive_intensity=emissive_intensity, emissive_map=emissive_map, normal_map=normal_map, normal_scale=normal_scale, metalness_roughness_map=metalness_roughness_map, occlusion_map=occlusion_map, occlusion_strength=occlusion_strength, alpha_mode=alpha_mode, alpha_cutoff=alpha_cutoff, transmission=transmission, ior=ior, thickness=thickness, attenuation_color=attenuation_color, attenuation_distance=attenuation_distance, slice_planes=slice_planes, name=name, group=group, custom_data=custom_data, compression_level=compression_level, visible=visible, click_callback=click_callback, hover_callback=hover_callback, ), **kwargs, )
# noinspection PyShadowingNames
[docs] def stl( stl: Union[str, bytes], color: int = _default_color, wireframe: bool = False, flat_shading: bool = True, roughness: float = 0.4, metalness: float = 0.0, shininess: float = None, name: Optional[str] = None, group: Optional[str] = None, custom_data: Optional[TypingDict[str, Any]] = None, compression_level: int = 0, visible: bool = True, **kwargs: Any, ) -> STL: """Create an STL drawable for data in STereoLitograpy format. Parameters ---------- stl : `str` or `bytes` STL data in either ASCII STL (`str`) or Binary STL (`bytes`). color : int, optional Hex color of the mesh, by default _default_color. wireframe : bool, optional Display the mesh as wireframe, by default False. flat_shading : bool, optional Display the mesh with flat shading, by default True. roughness: `float`. Roughness of object material. metalness: `float`. Metalness of object material. name : str, optional Object name, by default None. group : str, optional Name of a group, by default None. custom_data: `dict` A object with custom data attached to object. compression_level : int, optional Level of data compression [-1, 9], by default 0. visible : bool, optional Whether the object is drawn. Default is True. **kwargs For other keyword-only arguments, see :ref:`process_transform_arguments`. Returns ------- STL STL Drawable. """ plain = isinstance(stl, str) return process_transform_arguments( STL( text=stl if plain else None, binary=stl if not plain else None, color=color, wireframe=wireframe, flat_shading=flat_shading, roughness=roughness, metalness=metalness, shininess=shininess, name=name, group=group, custom_data=custom_data, compression_level=compression_level, visible=visible, ), **kwargs, )
[docs] def surface( heights: ArrayLike, color: int = _default_color, wireframe: bool = False, flat_shading: bool = True, roughness: float = 0.4, metalness: float = 0.0, shininess: float = None, attribute: ArrayLike = None, color_map: Optional[ColorMap] = None, color_range: ColorRange = None, opacity: float = 1.0, name: Optional[str] = None, group: Optional[str] = None, custom_data: Optional[TypingDict[str, Any]] = None, compression_level: int = 0, visible: bool = True, click_callback: Optional[Callable] = None, hover_callback: Optional[Callable] = None, **kwargs: Any, ) -> Surface: """Create a Surface drawable. Plot a 2d function: z = f(x, y). The default domain of the scalar field is -0.5 < x, y < 0.5. If the domain should be different, the bounding box needs to be transformed using `kwargs` - ``surface(..., bounds=[-1, 1, -1, 1])`` - ``surface(..., xmin=-10, xmax=10, ymin=-4, ymax=4)`` Parameters ---------- heights : array_like Array of `float` values. color : int, optional Hex color of the surface, by default _default_color. wireframe : bool, optional Display the mesh as wireframe, by default False. flat_shading : bool, optional Display the mesh with flat shading, by default True. roughness: `float`. Roughness of object material. metalness: `float`. Metalness of object material. attribute: list, optional List of values used to apply `color_map`, by default []. opacity: `float`. Opacity of surface. color_map : list, optional List of `float` quadruplets (attribute value, R, G, B) sorted by attribute value, by default None. The first quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. color_range : list, optional [min_value, max_value] pair determining the levels of color attribute mapped to 0 and 1 in the colormap, by default []. name : str, optional Object name, by default None. group : str, optional Name of a group, by default None. custom_data: `dict` A object with custom data attached to object. compression_level : int, optional Level of data compression [-1, 9], by default 0. visible : bool, optional Whether the object is drawn. Default is True. click_callback : callable, optional Called with the picking parameters when the object is clicked, while the plot is in mode='callback'. Default is None. hover_callback : callable, optional Called with the picking parameters when the cursor is over the object, while the plot is in mode='callback'. Default is None. **kwargs For other keyword-only arguments, see :ref:`process_transform_arguments`. Returns ------- Surface Surface Drawable. """ if attribute is None: attribute = [] if color_range is None: color_range = [] if color_map is None: color_map = default_colormap color_map = ( np.array(color_map, np.float32) if type(color_map) is not dict else color_map ) attribute = ( np.array(attribute, np.float32) if type(attribute) is not dict else attribute ) color_range = check_attribute_color_range(attribute, color_range) return process_transform_arguments( Surface( heights=heights, color=color, wireframe=wireframe, flat_shading=flat_shading, roughness=roughness, metalness=metalness, shininess=shininess, attribute=attribute, color_map=color_map, color_range=color_range, opacity=opacity, name=name, group=group, custom_data=custom_data, compression_level=compression_level, visible=visible, click_callback=click_callback, hover_callback=hover_callback, ), **kwargs, )