"""Module for creating various materials used in 3D rendering."""
import numpy as np
from fury.lib import (
ImageBasicMaterial,
LineArrowMaterial,
LineMaterial,
LineSegmentMaterial,
LineThinMaterial,
LineThinSegmentMaterial,
MeshBasicMaterial,
MeshPhongMaterial,
PointsGaussianBlobMaterial,
PointsMarkerMaterial,
PointsMaterial,
TextMaterial,
)
[docs]
def validate_opacity(opacity):
"""
Ensure opacity is between 0 and 1.
Parameters
----------
opacity : float
Opacity value to validate.
Returns
-------
float
Validated opacity value.
Raises
------
ValueError
If opacity is not between 0 and 1.
"""
if opacity is None:
return 1.0
if not (0 <= opacity <= 1):
raise ValueError("Opacity must be between 0 and 1.")
return opacity
[docs]
def validate_color(color, opacity, mode):
"""
Validate and modify color based on opacity and mode.
Parameters
----------
color : tuple or None
RGB or RGBA color tuple.
opacity : float
Opacity value between 0 and 1.
mode : str
Color mode, either 'auto' or 'vertex'.
Returns
-------
tuple or None
Modified color tuple with opacity applied.
Raises
------
ValueError
If color is None when mode is 'auto' or if color has invalid length.
"""
if color is None and mode == "auto":
return (1, 1, 1, opacity)
if mode == "vertex":
return (1, 1, 1)
if color is not None:
if len(color) == 3:
return (*color, opacity)
elif len(color) == 4:
return (*color[:3], color[3] * opacity)
else:
raise ValueError("Color must be a tuple of length 3 or 4.")
return color
def _create_mesh_material(
*,
material="phong",
enable_picking=True,
color=None,
opacity=1.0,
mode="vertex",
flat_shading=True,
texture=None,
wireframe=False,
wireframe_thickness=-1.0,
alpha_mode="blend",
depth_write=True,
):
"""
Create a mesh material.
Parameters
----------
material : str, optional
The type of material to create. Options are 'phong' (default) and
'basic'.
enable_picking : bool, optional
Whether the material should be pickable in a scene.
color : tuple or None, optional
The color of the material, represented as an RGB or RGBA tuple. If None,
the default color is used.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity.
mode : str, optional
The color mode of the material. Options are 'auto' and 'vertex'.
flat_shading : bool, optional
Whether to use flat shading (True) or smooth shading (False).
texture : Texture or TextureMap, optional
The texture map specifying the color for each texture coordinate.
wireframe : bool, optional
Whether to render the mesh as a wireframe.
wireframe_thickness : float, optional
The thickness of the wireframe lines.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
MeshMaterial
A mesh material object of the specified type with the given properties.
Raises
------
ValueError
If an unsupported material type is specified.
"""
opacity = validate_opacity(opacity)
color = validate_color(color, opacity, mode)
args = {
"pick_write": enable_picking,
"color_mode": mode,
"color": color,
"opacity": opacity,
"flat_shading": flat_shading,
"map": texture,
"wireframe": wireframe,
"wireframe_thickness": wireframe_thickness,
"alpha_mode": alpha_mode,
"depth_write": depth_write,
}
if material == "phong":
return MeshPhongMaterial(**args)
elif material == "basic":
return MeshBasicMaterial(**args)
else:
raise ValueError(f"Unsupported material type: {material}")
def _create_line_material(
*,
material="basic",
enable_picking=True,
color=None,
opacity=1.0,
mode="auto",
thickness=2.0,
thickness_space="screen",
dash_pattern=(),
dash_offset=0.0,
anti_aliasing=True,
alpha_mode="blend",
depth_write=True,
):
"""
Create a line material.
Parameters
----------
material : str, optional
The type of line material to create. Options are 'line' (default),
'segment', 'arrow', 'thin', and 'thin_segment'.
enable_picking : bool, optional
Whether the material should be pickable in a scene.
color : tuple or None, optional
The color of the material, represented as an RGBA tuple. If None, the
default color is used.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity.
mode : str, optional
The color mode of the material. Options are 'auto' and 'vertex'.
thickness : float, optional
The line thickness expressed in logical pixels.
thickness_space : str, optional
The coordinate space in which the thickness is
expressed ('screen', 'world', 'model').
dash_pattern : tuple, optional
The pattern of the dash, e.g., [2, 3].
meaning no dashing.
dash_offset : float, optional
The offset into the dash cycle to start drawing at.
anti_aliasing : bool, optional
Whether or not the line is anti-aliased in the shader.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
LineMaterial
A line material object of the specified type with the given properties.
"""
opacity = validate_opacity(opacity)
color = validate_color(color, opacity, mode)
args = {
"pick_write": enable_picking,
"color_mode": mode,
"color": color,
"thickness": thickness,
"thickness_space": thickness_space,
"dash_pattern": dash_pattern,
"dash_offset": dash_offset,
"aa": anti_aliasing,
"alpha_mode": alpha_mode,
"depth_write": depth_write,
}
if material == "basic":
return LineMaterial(**args)
elif material == "segment":
return LineSegmentMaterial(**args)
elif material == "arrow":
return LineArrowMaterial(**args)
elif material == "thin":
return LineThinMaterial(**args)
elif material == "thin_segment":
return LineThinSegmentMaterial(**args)
else:
raise ValueError(f"Unsupported material type: {material}")
def _create_vector_field_material(
cross_section,
*,
visibility=None,
material="thin_line",
enable_picking=True,
opacity=1.0,
thickness=1.0,
thickness_space="screen",
anti_aliasing=True,
alpha_mode="blend",
depth_write=True,
):
"""
Create a line material.
Parameters
----------
cross_section : list or tuple, shape (3,), optional
A list or tuple representing the cross section dimensions.
If None, the cross section will be ignored and complete field will be shown.
visibility : list or tuple, shape (3,), optional
A list or tuple representing the visibility in the x, y, and z dimensions.
If None, the visibility will be set to (-1, -1, -1) to show the complete field.
material : str, optional
The type of vector field material to create. Options are 'thin_line' (default),
'line', 'arrow'.
enable_picking : bool, optional
Whether the material should be pickable in a scene.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity.
thickness : float, optional
The line thickness expressed in logical pixels.
thickness_space : str, optional
The coordinate space in which the thickness is
expressed ('screen', 'world', 'model').
anti_aliasing : bool, optional
Whether or not the line is anti-aliased in the shader.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
LineMaterial
A line material object of the specified type with the given properties.
"""
opacity = validate_opacity(opacity)
args = {
"pick_write": enable_picking,
"opacity": opacity,
"thickness": thickness,
"thickness_space": thickness_space,
"aa": anti_aliasing,
"alpha_mode": alpha_mode,
"depth_write": depth_write,
}
if material == "thin_line":
return VectorFieldThinLineMaterial(cross_section, visibility=visibility, **args)
elif material == "line":
return VectorFieldLineMaterial(cross_section, visibility=visibility, **args)
elif material == "arrow":
return VectorFieldArrowMaterial(cross_section, visibility=visibility, **args)
else:
raise ValueError(f"Unsupported material type: {material}")
def _create_points_material(
*,
material="basic",
color=(1.0, 1.0, 1.0),
size=4.0,
map=None,
aa=True,
marker="circle",
edge_color="black",
edge_width=1.0,
mode="vertex",
opacity=1.0,
enable_picking=True,
alpha_mode="blend",
depth_write=True,
):
"""
Create a points material.
Parameters
----------
material : str, optional
The type of material to create. Options are 'basic' (default),
'gaussian', and 'marker'.
color : tuple, optional
RGB or RGBA values in the range [0, 1].
size : float, optional
The size (diameter) of the points in logical pixels.
map : TextureMap or Texture, optional
The texture map specifying the color for each texture coordinate.
aa : bool, optional
Whether or not the points are anti-aliased in the shader.
marker : str or MarkerShape, optional
The shape of the marker.
Options are "●": "circle", "+": "plus", "x": "cross", "♥": "heart",
"✳": "asterix".
edge_color : str or tuple or Color, optional
The color of line marking the edge of the markers.
edge_width : float, optional
The width of the edge of the markers.
mode : str, optional
The color mode of the material. Options are 'auto' and 'vertex'.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity.
enable_picking : bool, optional
Whether the material should be pickable in a scene.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
PointsMaterial
A point material object of the specified type with the given properties.
Raises
------
ValueError
If an unsupported material type is specified.
"""
opacity = validate_opacity(opacity)
color = validate_color(color, 1.0, mode)
args = {
"color": color,
"size": size,
"color_mode": mode,
"map": map,
"aa": aa,
"pick_write": enable_picking,
"opacity": opacity,
"alpha_mode": alpha_mode,
"depth_write": depth_write,
}
if material == "basic":
return PointsMaterial(**args)
elif material == "gaussian":
return PointsGaussianBlobMaterial(**args)
elif material == "marker":
return PointsMarkerMaterial(
marker=marker, edge_color=edge_color, edge_width=edge_width, **args
)
else:
raise ValueError(f"Unsupported material type: {material}")
def _create_text_material(
*,
color=(0, 0, 0),
opacity=1.0,
outline_color=(0, 0, 0),
outline_thickness=0.0,
weight_offset=1.0,
aliasing=True,
alpha_mode="blend",
depth_write=True,
):
"""
Create a text material.
Parameters
----------
color : tuple, optional
The color of the text as RGB or RGBA tuple.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity.
outline_color : tuple, optional
The color of the outline of the text as RGB or RGBA tuple.
outline_thickness : float, optional
A value indicating the relative width of the outline. Valid values are
between 0.0 and 0.5.
weight_offset : float, optional
A value representing an offset to the font weight. Font weights are in
the range 100-900, so this value should be in the same order of
magnitude. Can be negative to make text thinner.
aliasing : bool, optional
If True, use anti-aliasing while rendering glyphs. Aliasing gives
prettier results, but may affect performance for very large texts.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
TextMaterial
A text material object with the specified properties.
"""
opacity = validate_opacity(opacity)
if color is not None:
if len(color) == 3:
color = (*color, opacity)
elif len(color) == 4:
color = color
color = (*color[:3], color[3] * opacity)
else:
raise ValueError("Color must be a tuple of length 3 or 4.")
return TextMaterial(
color=color,
outline_color=outline_color,
outline_thickness=outline_thickness,
weight_offset=weight_offset,
aa=aliasing,
alpha_mode=alpha_mode,
depth_write=depth_write,
)
def _create_image_material(
*,
clim=None,
map=None,
gamma=1.0,
interpolation="nearest",
alpha_mode="blend",
depth_write=True,
):
"""
Rasterized image material.
Parameters
----------
clim : tuple, optional
The contrast limits to scale the data values with.
map : Texture or TextureMap, optional
The texture map to turn the image values into its final color.
gamma : float, optional
The gamma correction to apply to the image data.
Must be greater than 0.0.
interpolation : str, optional
The method to interpolate the image data.
Either 'nearest' or 'linear'.
alpha_mode : str, optional
The alpha mode for the material. Please see the below link for details:
https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode.
depth_write : bool, optional
Whether to write depth information for the material.
Returns
-------
ImageMaterial
A rasterized image material object with the specified properties.
"""
return ImageBasicMaterial(
clim=clim,
map=map,
gamma=gamma,
interpolation=interpolation,
alpha_mode=alpha_mode,
depth_write=depth_write,
)
[docs]
class VectorFieldThinLineMaterial(LineMaterial):
"""
Material for VectorFieldActor.
Parameters
----------
cross_section : {list, tuple, ndarray}
A list or tuple or ndarray representing the cross section dimensions.
visibility : {list, tuple, ndarray}, optional
A list or tuple or ndarray representing the visibility in the 3D.
If None, the visibility will be set to (-1, -1, -1) to show the complete field.
**kwargs : dict
Additional keyword arguments for the material.
"""
uniform_type = dict(
LineThinSegmentMaterial.uniform_type,
cross_section="4xf4", # vec4<i32>
visibility="4xi4", # vec4<i32>
)
[docs]
def __init__(self, cross_section, *, visibility=None, **kwargs):
"""
Initialize the VectorFieldMaterial.
Parameters
----------
cross_section : {list, tuple, ndarray}
A list or tuple or ndarray representing the cross section dimensions.
visibility : {list, tuple, ndarray}, optional
A list or tuple or ndarray representing the visibility in the 3D.
If None, the visibility will be set to (-1, -1, -1) to show the complete
field.
**kwargs : dict
Additional keyword arguments for the material.
"""
super().__init__(color_mode="vertex", **kwargs)
self.cross_section = cross_section
self.visibility = visibility
@property
def visibility(self):
"""
Get the visibility of the vector field in each dimension.
Returns
-------
list
A list representing the visibility in the x, y, and z dimensions.
"""
vis = self.uniform_buffer.data["visibility"][:3]
if all(vis == (-1, -1, -1)):
return None
return [bool(i) for i in vis]
@visibility.setter
def visibility(self, visibility):
"""
Set the visibility of the vector field in each dimension.
Parameters
----------
visibility : list or tuple
A list or tuple representing the visibility in the x, y, and z dimensions.
"""
if visibility is None:
self.uniform_buffer.data["visibility"] = np.asarray(
[-1, -1, -1, 0], dtype=np.int32
)
else:
if len(visibility) != 3:
raise ValueError("visibility must have exactly 3 dimensions.")
if not all(
isinstance(i, bool)
or (hasattr(i, "item") and isinstance(i.item(), bool))
for i in visibility
):
raise ValueError("visibility must contain only booleans.")
self.uniform_buffer.data["visibility"] = np.asarray(
[*visibility, 0], dtype=np.int32
)
self.uniform_buffer.update_full()
@property
def cross_section(self):
"""
Get the cross section of the vector field.
Returns
-------
list
A list representing the cross section dimensions.
"""
return self.uniform_buffer.data["cross_section"][:3]
@cross_section.setter
def cross_section(self, cross_section):
"""
Set the cross section of the vector field.
Parameters
----------
cross_section : list or tuple
A list or tuple representing the cross section dimensions.
"""
if len(cross_section) != 3:
raise ValueError("cross_section must have exactly 3 dimensions.")
self.uniform_buffer.data["cross_section"] = np.asarray(
[*cross_section, 0], dtype=np.int32
)
self.uniform_buffer.update_full()
[docs]
class VectorFieldLineMaterial(VectorFieldThinLineMaterial):
"""
Material for VectorFieldActor.
This class provides a way to distinguish the usage of right shader
for creating a vector field.
"""
[docs]
class VectorFieldArrowMaterial(VectorFieldThinLineMaterial):
"""
Material for VectorFieldActor.
This class provides a way to distinguish the usage of right shader
for creating a vector field.
"""
[docs]
class SphGlyphMaterial(MeshPhongMaterial):
"""
Initialize the Spherical Glyph Material.
Parameters
----------
n_coeffs : int, optional
The maximum spherical harmonic degree.
scale : int, optional
The scale factor.
shininess : int, optional
The shininess factor.
emissive : str, optional
The emissive color.
specular : str, optional
The specular color.
**kwargs : dict
Additional keyword arguments for the material.
"""
uniform_type = dict(
MeshPhongMaterial.uniform_type,
n_coeffs="i4",
scale="f4",
)
[docs]
def __init__(
self,
n_coeffs=-1,
scale=1,
shininess=30,
emissive="#000",
specular="#494949",
**kwargs,
):
"""
Initialize the Spherical Glyph Material.
Parameters
----------
n_coeffs : int, optional
The maximum spherical harmonic degree. This value will limit the number of
spherical harmonic coefficients that can be used from the data.
If -1, no limit is applied.
scale : int, optional
The scale factor.
shininess : int, optional
The shininess factor.
emissive : str, optional
The emissive color.
specular : str, optional
The specular color.
**kwargs : dict
Additional keyword arguments for the material.
"""
super().__init__(shininess, emissive, specular, **kwargs)
self.n_coeffs = n_coeffs
self.scale = scale
@property
def n_coeffs(self):
"""
Get the maximum number of spherical harmonic coefficients.
Returns
-------
int
The maximum number of spherical harmonic coefficients.
"""
return self.uniform_buffer.data["n_coeffs"]
@n_coeffs.setter
def n_coeffs(self, value):
"""
Set the maximum number of spherical harmonic coefficients.
Parameters
----------
value : int
The maximum number of spherical harmonic coefficients.
"""
if not isinstance(value, int):
raise ValueError("n_coeffs must be an integer.")
self.uniform_buffer.data["n_coeffs"] = value
self.uniform_buffer.update_full()
@property
def scale(self):
"""
Get the scale factor.
Returns
-------
float
The scale factor.
"""
return self.uniform_buffer.data["scale"]
@scale.setter
def scale(self, value):
"""
Set the scale factor.
Parameters
----------
value : float
The scale factor.
"""
if not isinstance(value, (int, float)):
raise ValueError("scale must be a number.")
self.uniform_buffer.data["scale"] = float(value)
self.uniform_buffer.update_full()
[docs]
class StreamlinesMaterial(LineMaterial):
"""
Initialize the Streamlines Material.
Parameters
----------
outline_thickness : float, optional
The thickness of the outline.
outline_color : tuple, optional
The color of the outline as an RGBA tuple.
roi_enabled : bool, optional
Whether ROI culling is active. This is typically driven by the presence
of an ROI mask on the actor.
roi_dim : tuple, optional
3D integer dimensions (nx, ny, nz) of a mask grid when a volumetric
ROI is used in the shader.
**kwargs : dict
Additional keyword arguments for the material.
"""
uniform_type = dict(
LineMaterial.uniform_type,
outline_thickness="f4",
outline_color="4xf4",
roi_enabled="i4",
roi_dim="4xi4",
)
[docs]
def __init__(
self,
outline_thickness=0.0,
outline_color=(0, 0, 0),
roi_enabled=None,
roi_dim=(0, 0, 0),
**kwargs,
):
"""
Initialize the Streamline Material.
Parameters
----------
outline_thickness : float, optional
The thickness of the outline.
outline_color : tuple, optional
The color of the outline as an RGBA tuple.
roi_enabled : bool, optional
Whether ROI culling is active. If None, ROI culling defaults to
False until an ROI mask is attached by the actor.
roi_dim : tuple, optional
3D integer dimensions (nx, ny, nz) of a mask grid when a volumetric
ROI is used in the shader.
**kwargs : dict
Additional keyword arguments for the material.
"""
super().__init__(**kwargs)
self.outline_thickness = outline_thickness
self.outline_color = outline_color
self.roi_dim = roi_dim
self.roi_enabled = roi_enabled
@property
def outline_thickness(self):
"""
Get the outline thickness.
Returns
-------
float
The thickness of the outline.
"""
return float(self.uniform_buffer.data["outline_thickness"])
@outline_thickness.setter
def outline_thickness(self, value):
"""
Set the outline thickness.
Parameters
----------
value : float
The thickness of the outline.
"""
self.uniform_buffer.data["outline_thickness"] = float(value)
self.uniform_buffer.update_full()
@property
def outline_color(self):
"""
Get the outline color.
Returns
-------
tuple
The color of the outline as an RGBA tuple.
"""
return self.uniform_buffer.data["outline_color"][:3]
@outline_color.setter
def outline_color(self, value):
"""
Set the outline color.
Parameters
----------
value : tuple
The color of the outline as an RGB or RGBA tuple.
"""
if len(value) == 3:
value = (*value, 1.0)
self.uniform_buffer.data["outline_color"] = value
self.uniform_buffer.update_full()
@property
def roi_enabled(self):
"""
Return True when ROI-based culling is active.
Returns
-------
bool
True if ROI-based culling is enabled, False otherwise.
"""
return bool(self.uniform_buffer.data["roi_enabled"])
@roi_enabled.setter
def roi_enabled(self, value):
"""
Enable/disable ROI-based culling.
Parameters
----------
value : bool
True to enable ROI-based culling, False to disable.
"""
self.uniform_buffer.data["roi_enabled"] = int(bool(value))
self.uniform_buffer.update_full()
@property
def roi_dim(self):
"""
ROI grid dimensions as (nx, ny, nz).
Returns
-------
tuple
A tuple of three integers representing the ROI grid dimensions.
"""
return tuple(int(x) for x in self.uniform_buffer.data["roi_dim"][:3])
@roi_dim.setter
def roi_dim(self, value):
"""
Set the ROI grid dimensions.
Parameters
----------
value : tuple
A tuple of three integers representing the ROI grid dimensions.
"""
dims = np.asarray(value, dtype=np.int32).reshape(-1)
if dims.size != 3:
raise ValueError("roi_dim must contain exactly three integers.")
self.uniform_buffer.data["roi_dim"] = (
int(dims[0]),
int(dims[1]),
int(dims[2]),
0,
)
self.uniform_buffer.update_full()
class _StreamlineBakedMaterial(StreamlinesMaterial):
"""
Initialize the internal baked streamline material.
Parameters
----------
auto_detach : bool, optional
If True, automatically switch to render-only material after baking.
**kwargs : dict
Additional keyword arguments for the material.
"""
def __init__(self, *, auto_detach=True, **kwargs):
"""
Initialize the internal baked streamline material.
Parameters
----------
auto_detach : bool, optional
If True, automatically switch to render-only material after baking.
**kwargs : dict
Additional keyword arguments for the material.
"""
super().__init__(**kwargs)
self.auto_detach = bool(auto_detach)
[docs]
class BillboardMaterial(MeshBasicMaterial):
"""
Billboard material for creating quads that always face the camera.
This material is designed to work with the BillboardShader to create
rectangles that automatically rotate to face the camera while maintaining
their 3D position.
Parameters
----------
**kwargs : dict
Additional keyword arguments forwarded to
:class:`~fury.material.MeshBasicMaterial`.
"""
[docs]
def __init__(self, **kwargs):
"""
Initialize the material and forward arguments to the base class.
Parameters
----------
**kwargs : dict
Additional keyword arguments forwarded to ``MeshBasicMaterial``.
"""
super().__init__(**kwargs)
[docs]
class StreamtubeMaterial(MeshPhongMaterial):
"""
Material for GPU-generated streamtubes.
Parameters
----------
**kwargs : dict, optional
Arguments forwarded to :class:`MeshPhongMaterial`.
"""
[docs]
def __init__(self, **kwargs):
"""
Initialise the material with MeshPhongMaterial keyword arguments.
Parameters
----------
**kwargs : dict, optional
Arguments forwarded to :class:`MeshPhongMaterial`.
"""
super().__init__(**kwargs)
class _StreamtubeBakedMaterial(MeshPhongMaterial):
"""
Internal material for compute-shader-based streamtubes with auto-baking.
This material is used internally by the streamtube actor when GPU compute
shaders are available. Users should not instantiate this directly; instead
use the ``streamtube()`` function which will automatically select the
appropriate implementation.
Parameters
----------
radius : float, optional
Tube radius used by the compute shader.
segments : int, optional
Number of radial segments forming the tube.
end_caps : bool, optional
Whether flat caps are generated for each tube.
auto_detach : bool, optional
If True, automatically switch to render-only material after baking.
**kwargs : dict, optional
Additional arguments forwarded to :class:`MeshPhongMaterial`.
Notes
-----
This is an internal class marked with a leading underscore. It should not
be used directly by end users.
"""
uniform_type = dict(
MeshPhongMaterial.uniform_type,
tube_radius="f4",
tube_segments="u4",
tube_end_caps="i4",
line_count="u4",
)
def __init__(
self,
*,
radius=0.2,
segments=8,
end_caps=True,
auto_detach=True,
**kwargs,
):
"""
Initialise uniforms controlling the compute-driven streamtube.
Parameters
----------
radius : float, optional
Tube radius used by the compute shader.
segments : int, optional
Number of radial segments forming the tube.
end_caps : bool, optional
Whether flat caps are generated for each tube.
auto_detach : bool, optional
If True, automatically switch to render-only material after baking.
**kwargs : dict, optional
Additional arguments forwarded to :class:`MeshPhongMaterial`.
"""
super().__init__(**kwargs)
self.radius = radius
self.segments = segments
self.end_caps = end_caps
self.line_count = 0
self.auto_detach = bool(auto_detach)
@property
def radius(self):
"""
Get the tube radius used by the compute shader.
Returns
-------
float
Current tube radius.
"""
return float(self.uniform_buffer.data["tube_radius"])
@radius.setter
def radius(self, value):
"""
Set the tube radius used in the compute shader.
Parameters
----------
value : float
New tube radius value.
"""
self.uniform_buffer.data["tube_radius"] = float(value)
self.uniform_buffer.update_full()
@property
def segments(self):
"""
Get the number of radial segments per tube.
Returns
-------
int
Number of radial segments.
"""
return int(self.uniform_buffer.data["tube_segments"])
@segments.setter
def segments(self, value):
"""
Set the number of radial segments per tube.
Parameters
----------
value : int
New segment count.
"""
self.uniform_buffer.data["tube_segments"] = int(value)
self.uniform_buffer.update_full()
@property
def end_caps(self):
"""
Check whether end caps are rendered.
Returns
-------
bool
``True`` when end caps are enabled.
"""
return bool(self.uniform_buffer.data["tube_end_caps"])
@end_caps.setter
def end_caps(self, value):
"""
Enable or disable end caps on the tubes.
Parameters
----------
value : bool
Flag indicating whether to render end caps.
"""
self.uniform_buffer.data["tube_end_caps"] = int(bool(value))
self.uniform_buffer.update_full()
@property
def line_count(self):
"""
Get the number of lines processed by the compute shader.
Returns
-------
int
Number of lines passed to the GPU compute stage.
"""
return int(self.uniform_buffer.data["line_count"])
@line_count.setter
def line_count(self, value):
"""
Update the number of lines handled by the compute shader.
Parameters
----------
value : int
Number of lines to process.
"""
self.uniform_buffer.data["line_count"] = int(value)
self.uniform_buffer.update_full()
def _setup_compute_shader(self, line_count, max_line_length, tube_segments):
"""
Record metadata used by the GPU compute pass.
Parameters
----------
line_count : int
Total number of lines to process.
max_line_length : int
Maximum length of any line in the batch.
tube_segments : int
Number of radial tube segments.
"""
self.line_count = line_count
self.segments = tube_segments
self._max_line_length = max_line_length
[docs]
class BillboardSphereMaterial(MeshPhongMaterial):
"""
Phong-lit material for billboard-based impostor spheres.
Parameters
----------
**material_kwargs : dict
Keyword arguments propagated to
:class:`~fury.material.MeshPhongMaterial`.
"""
[docs]
def __init__(self, **material_kwargs):
"""
Initialize the sphere impostor material.
Parameters
----------
**material_kwargs : dict
Keyword arguments propagated to
:class:`~fury.material.MeshPhongMaterial`.
"""
material_kwargs.setdefault("flat_shading", False)
super().__init__(**material_kwargs)