Delaunay3DDemo
Repository source: Delaunay3DDemo
Description¶
This example creates a tetrahedral mesh from unorganized points. The example uses the vtkDelaunay3D filter. The resulting mesh will be a solid convex hull of the original points. The example takes the points from a XML PolyData file (.vtp) produces the 3D Delaunay tetrahedralization (both with alpha = 0 and a non-zero alpha), and displays the result on the screen.
For alpha != 0 (right window), the tetra are yellow, the lines are blue and the triangles are red.
Alpha can be changed interactively to see its effect on the resulting surface.
Other languages
See (Cxx)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
Delaunay3DDemo.py
#!/usr/bin/env python3
from dataclasses import dataclass
from pathlib import Path
from typing import Tuple
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingFreeType
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import (
vtkUnsignedCharArray, vtkCommand
)
from vtkmodules.vtkCommonDataModel import (
VTK_LINE, VTK_TETRA, VTK_TRIANGLE, VTK_VERTEX
)
from vtkmodules.vtkFiltersCore import (
vtkCleanPolyData,
vtkDelaunay3D
)
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkIOGeometry import (
vtkBYUReader,
vtkOBJReader,
vtkSTLReader
)
from vtkmodules.vtkIOLegacy import vtkPolyDataReader
from vtkmodules.vtkIOPLY import vtkPLYReader
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
from vtkmodules.vtkInteractionWidgets import (
vtkSliderRepresentation2D,
vtkSliderWidget,
vtkTextRepresentation,
vtkTextWidget
)
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkCamera,
vtkDataSetMapper,
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkTextProperty,
vtkTextActor
)
def get_program_parameters():
import argparse
description = 'Delaunay 3D demonstration.'
epilogue = '''
If no filename is specified, a sphere is used.
'''
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('file_name', type=str, nargs='?', default='',
help='The path to the polydata file e.g. Bunny.vtp.')
args = parser.parse_args()
return args.file_name
def main():
colors = vtkNamedColors()
file_name = get_program_parameters()
if file_name:
fn = Path(file_name)
if not fn.is_file():
print(f'{fn}\nNot found.')
return
else:
poly_data = read_poly_data(file_name=fn)
else:
source = vtkSphereSource(phi_resolution=50, theta_resolution=50)
poly_data = source.update().output
original_mapper = vtkDataSetMapper(input_data=poly_data, scalar_visibility=False,
scalar_mode=Mapper.ScalarMode.VTK_SCALAR_MODE_USE_CELL_DATA)
original_actor = vtkActor(mapper=original_mapper)
original_actor.property.color = colors.GetColor3d('Tomato')
original_actor.property.interpolation = Property.Interpolation.VTK_FLAT
# Clean the polydata. This will remove duplicate points that may be
# present in the input data.
cleaner = vtkCleanPolyData(input_data=poly_data)
# Generate a tetrahedral mesh from the input points. By
# default, the generated volume is the convex hull of the points.
delaunay_3d = vtkDelaunay3D()
delaunay_mapper = vtkDataSetMapper(scalar_mode=Mapper.ScalarMode.VTK_SCALAR_MODE_USE_CELL_DATA)
cleaner >> delaunay_3d >> delaunay_mapper
delaunay_actor = vtkActor(mapper=delaunay_mapper)
delaunay_actor.SetMapper(delaunay_mapper)
delaunay_actor.property.color = colors.GetColor3d('Banana')
delaunay_actor.property.interpolation = Property.Interpolation.VTK_FLAT
# Generate a mesh from the input points. If Alpha is non-zero, then
# tetrahedra, triangles, edges and vertices that lie within the
# alpha radius are output.
delaunay_3d_alpha = vtkDelaunay3D(alpha=0.0105)
cleaner >> delaunay_3d_alpha
# delaunay_3d_alpha.update()
cell_data, num_tetras, num_lines, num_tris, num_verts = get_cell_types(delaunay_3d_alpha, colors)
print(f'Tetras: {num_tetras} Lines: {num_lines} Triangles: {num_tris} Vertices: {num_verts}')
ss = f' Tetras: {num_tetras:3d}\n Lines: {num_lines:3d}\nTriangles: {num_tris:3d}\n Vertices: {num_verts:3d}'
# Set the cell color depending on cell type.
delaunay_3d_alpha.output.GetCellData().SetScalars(cell_data)
delaunay_alpha_mapper = vtkDataSetMapper(scalar_mode=Mapper.ScalarMode.VTK_SCALAR_MODE_USE_CELL_DATA)
cleaner >> delaunay_3d_alpha >> delaunay_alpha_mapper
delaunay_alpha_actor = vtkActor(mapper=delaunay_alpha_mapper)
delaunay_alpha_actor.property.point_size = 5.0
delaunay_alpha_actor.property.interpolation = Property.Interpolation.VTK_FLAT
# Visualize
# Define viewport ranges.
# (xmin, ymin, xmax, ymax)
left_viewport = [0.0, 0.0, 0.33, 1.0]
center_viewport = [0.33, 0.0, 0.66, 1.0]
right_viewport = [0.66, 0.0, 1.0, 1.0]
# Shared camera
shared_camera = vtkCamera()
# Create the renderers, render window, and interactor.
original_renderer = vtkRenderer(viewport=left_viewport, use_hidden_line_removal=True,
background=colors.GetColor3d('Slate_Grey'), active_camera=shared_camera)
delaunay_renderer = vtkRenderer(viewport=center_viewport, use_hidden_line_removal=True,
background=colors.GetColor3d('Light_Grey'), active_camera=shared_camera)
delaunay_alpha_renderer = vtkRenderer(viewport=right_viewport, use_hidden_line_removal=True,
background=colors.GetColor3d('Grey'), active_camera=shared_camera)
render_window = vtkRenderWindow(size=(1200, 400), window_name='Delaunay3DDemo')
render_window_interactor = vtkRenderWindowInteractor()
render_window_interactor.render_window = render_window
render_window.AddRenderer(original_renderer)
render_window.AddRenderer(delaunay_renderer)
render_window.AddRenderer(delaunay_alpha_renderer)
text_property = vtkTextProperty(color=colors.GetColor3d('Black'), bold=True, italic=False, shadow=False,
line_spacing=1.0, font_family_as_string='Courier',
font_size=24,
justification=TextProperty.Justification.VTK_TEXT_LEFT,
vertical_justification=TextProperty.VerticalJustification.VTK_TEXT_BOTTOM)
text_actor = vtkTextActor(input=ss, text_scale_mode=vtkTextActor.TEXT_SCALE_MODE_VIEWPORT,
text_property=text_property)
# Create the text representation. Used for positioning the text actor.
text_representation = vtkTextRepresentation(enforce_normalized_viewport_bounds=True)
text_representation.position_coordinate.value = (0.005, 0.005)
text_representation.position2_coordinate.value = (0.3, 0.2)
# Create the text widget, setting the default renderer and interactor.
text_widget = vtkTextWidget(representation=text_representation, text_actor=text_actor,
default_renderer=delaunay_alpha_renderer, interactor=render_window_interactor,
selectable=False)
# Set up a slider widget for each varying parameter.
slider_properties = make_slider_2d_properties()
slider_properties.Colors.bar_ends_color = 'DarkSlateBlue'
slider_properties.Colors.bar_color = 'Gold'
slider_properties.Colors.title_color = 'Black'
slider_properties.Colors.label_color = 'Black'
slider_properties.Range.maximum_value = 0.0233
slider_properties.Range.minimum_value = 0.0001
slider_properties.Range.value = 0.0105
slider_properties.Dimensions.tube_width = 0.02
slider_properties.Dimensions.slider_length = 0.04
slider_properties.Dimensions.slider_width = 0.04
slider_properties.Dimensions.end_cap_length = slider_properties.Dimensions.tube_width * 1.5
slider_properties.Dimensions.slider_width = slider_properties.Dimensions.tube_width * 1.5
slider_properties.Dimensions.label_height = 0.035
slider_properties.Dimensions.title_height = 0.045
slider_properties.Position.point1 = (0.1, 0.1)
slider_properties.Position.point2 = (0.9, 0.1)
slider_properties.Text.title = 'Alpha'
slider_properties.Text.title_shadow = False
slider_properties.Text.label_shadow = False
slider_widget_alpha = make_2d_slider_widget(slider_properties, render_window_interactor)
slider_widget_alpha.AddObserver(vtkCommand.InteractionEvent,
SliderCallbackAlpha(delaunay_3d_alpha, text_actor, colors))
original_renderer.AddActor(original_actor)
delaunay_renderer.AddActor(delaunay_actor)
delaunay_alpha_renderer.AddActor(delaunay_alpha_actor)
delaunay_alpha_renderer.AddViewProp(text_actor)
original_renderer.ResetCamera()
render_window.Render()
text_widget.On()
# Render and interact.
render_window_interactor.Start()
def make_slider_2d_properties():
"""
This applies more specific values to the default Slider2DProperties.
:return: The slider 2D properties.
"""
sp = Slider2DProperties()
sp.Colors.title_color = 'AliceBlue'
sp.Colors.label_color = 'AliceBlue'
sp.Colors.slider_color = 'Green'
sp.Colors.bar_color = 'MistyRose'
sp.Colors.bar_ends_color = 'Yellow'
sp.Colors.selected_color = 'DeepPink'
sp.Dimensions.slider_length = 0.05
sp.Dimensions.slider_width = 0.025
sp.Dimensions.end_cap_length = 0.02
sp.Dimensions.title_height = 0.045
sp.Dimensions.label_height = 0.035
sp.Position.point1 = (0.3, 0.1)
sp.Position.point2 = (0.7, 0.1)
sp.Range.minimum_value = 3
sp.Range.maximum_value = 20
return sp
def make_2d_slider_widget(properties, interactor):
"""
Make a 2D slider widget.
:param properties: The 2D slider properties.
:param interactor: The vtkInteractor.
:return: The slider widget.
"""
colors = vtkNamedColors()
slider_rep = vtkSliderRepresentation2D(minimum_value=properties.Range.minimum_value,
maximum_value=properties.Range.maximum_value,
value=properties.Range.value,
title_text=properties.Text.title,
tube_width=properties.Dimensions.tube_width,
slider_length=properties.Dimensions.slider_length,
slider_width=properties.Dimensions.slider_width,
end_cap_length=properties.Dimensions.end_cap_length,
end_cap_width=properties.Dimensions.end_cap_width,
title_height=properties.Dimensions.title_height,
label_height=properties.Dimensions.label_height,
)
# Set the color properties.
slider_rep.title_property.color = colors.GetColor3d(properties.Colors.title_color)
slider_rep.label_property.color = colors.GetColor3d(properties.Colors.label_color)
slider_rep.tube_property.color = colors.GetColor3d(properties.Colors.bar_color)
slider_rep.cap_property.color = colors.GetColor3d(properties.Colors.bar_ends_color)
slider_rep.slider_property.color = colors.GetColor3d(properties.Colors.slider_color)
slider_rep.selected_property.color = colors.GetColor3d(properties.Colors.selected_color)
# Set the position.
slider_rep.point1_coordinate.coordinate_system = properties.Position.coordinate_system
slider_rep.point1_coordinate.value = properties.Position.point1
slider_rep.point2_coordinate.coordinate_system = properties.Position.coordinate_system
slider_rep.point2_coordinate.value = properties.Position.point2
title_font_family = properties.Text.title_font_family
match title_font_family:
case 'Courier':
slider_rep.title_property.SetFontFamilyToCourier()
case 'Times':
slider_rep.title_property.SetFontFamilyToTimes()
case _:
slider_rep.title_property.SetFontFamilyToArial()
slider_rep.title_property.bold = properties.Text.title_bold
slider_rep.title_property.italic = properties.Text.title_italic
slider_rep.title_property.shadow = properties.Text.title_shadow
label_font_family = properties.Text.label_font_family
match label_font_family:
case 'Courier':
slider_rep.label_property.SetFontFamilyToCourier()
case 'Times':
slider_rep.label_property.SetFontFamilyToTimes()
case _:
slider_rep.label_property.SetFontFamilyToArial()
slider_rep.label_property.bold = properties.Text.label_bold
slider_rep.label_property.italic = properties.Text.label_italic
slider_rep.label_property.shadow = properties.Text.label_shadow
# widget = vtkSliderWidget(representation=slider_rep, interactor=interactor, enabled=True)
widget = vtkSliderWidget(interactor=interactor)
widget.SetRepresentation(slider_rep)
widget.EnabledOn()
widget.SetAnimationModeToAnimate()
widget.number_of_animation_steps = 2
return widget
@dataclass(frozen=True)
class Coordinate:
@dataclass(frozen=True)
class CoordinateSystem:
VTK_DISPLAY: int = 0
VTK_NORMALIZED_DISPLAY: int = 1
VTK_VIEWPORT: int = 2
VTK_NORMALIZED_VIEWPORT: int = 3
VTK_VIEW: int = 4
VTK_POSE: int = 5
VTK_WORLD: int = 6
VTK_USERDEFINED: int = 7
@dataclass
class Slider2DProperties:
@dataclass
class Colors:
# The color of the text indicating what the slider controls.
title_color: str = 'White'
# The color of the text displaying the value.
label_color: str = 'White'
# The color of the knob that slides.
slider_color: str = 'White'
# The color of the knob when the mouse is held on it.
selected_color: str = 'HotPink'
# The color of the bar.
bar_color: str = 'White'
# The color of the ends of the bar.
bar_ends_color: str = 'White'
@dataclass
class Dimensions:
tube_width: float = 0.008
slider_length: float = 0.01
slider_width: float = 0.02
end_cap_length: float = 0.005
end_cap_width: float = 0.05
title_height: float = 0.03
label_height: float = 0.025
@dataclass
class Position:
coordinate_system: int = Coordinate.CoordinateSystem.VTK_NORMALIZED_VIEWPORT
point1: Tuple = (0.1, 0.1)
point2: Tuple = (0.9, 0.1)
@dataclass
class Range:
minimum_value: float = 0.0
maximum_value: float = 1.0
value: float = 0.0
@dataclass
class Text:
# Font families are: Ariel, Courier and Times
title: str = ''
title_font_family = 'Arial'
title_bold: bool = True
title_italic: bool = False
title_shadow: bool = True
label_font_family = 'Arial'
label_bold: bool = True
label_italic: bool = False
label_shadow: bool = True
# This callback does the actual work.
class SliderCallbackAlpha:
def __init__(self, delaunay, text_mapper, colors):
self.delaunay = delaunay
self.text_mapper = text_mapper
self.colors = colors
def __call__(self, caller, ev):
slider_widget = caller
value = slider_widget.representation.value
self.delaunay.alpha = value
cell_data, num_tetras, num_lines, num_tris, num_verts = get_cell_types(self.delaunay, self.colors)
# Set the cell color depending on cell type.
self.delaunay.output.GetCellData().SetScalars(cell_data)
ss = f' Tetras: {num_tetras:3d}\n Lines: {num_lines:3d}\nTriangles: {num_tris:3d}\n Vertices: {num_verts:3d}'
self.text_mapper.SetInput(ss)
def get_cell_types(cells, colors):
cell_data = vtkUnsignedCharArray()
cell_data.SetNumberOfComponents(3)
num_tetras = 0
num_lines = 0
num_tris = 0
num_verts = 0
it = cells.update().output.NewCellIterator()
while not it.IsDoneWithTraversal():
if it.GetCellType() == VTK_TETRA:
num_tetras += 1
cell_data.InsertNextTypedTuple(colors.GetColor3ub('Banana'))
if it.GetCellType() == VTK_LINE:
num_lines += 1
cell_data.InsertNextTypedTuple(colors.GetColor3ub('Peacock'))
if it.GetCellType() == VTK_TRIANGLE:
num_tris += 1
cell_data.InsertNextTypedTuple(colors.GetColor3ub('Tomato'))
if it.GetCellType() == VTK_VERTEX:
num_verts += 1
cell_data.InsertNextTypedTuple(colors.GetColor3ub('Lime'))
it.GoToNextCell()
return cell_data, num_tetras, num_lines, num_tris, num_verts
def read_poly_data(file_name):
if not file_name:
print(f'No file name.')
return None
valid_suffixes = ['.g', '.obj', '.stl', '.ply', '.vtk', '.vtp']
path = Path(file_name)
ext = None
if path.suffix:
ext = path.suffix.lower()
if path.suffix not in valid_suffixes:
print(f'No reader for this file suffix: {ext}')
return None
reader = None
if ext == '.ply':
reader = vtkPLYReader(file_name=file_name)
elif ext == '.vtp':
reader = vtkXMLPolyDataReader(file_name=file_name)
elif ext == '.obj':
reader = vtkOBJReader(file_name=file_name)
elif ext == '.stl':
reader = vtkSTLReader(file_name=file_name)
elif ext == '.vtk':
reader = vtkPolyDataReader(file_name=file_name)
elif ext == '.g':
reader = vtkBYUReader(file_name=file_name)
if reader:
reader.update()
poly_data = reader.output
return poly_data
else:
return None
@dataclass(frozen=True)
class Coordinate:
@dataclass(frozen=True)
class CoordinateSystem:
VTK_DISPLAY: int = 0
VTK_NORMALIZED_DISPLAY: int = 1
VTK_VIEWPORT: int = 2
VTK_NORMALIZED_VIEWPORT: int = 3
VTK_VIEW: int = 4
VTK_POSE: int = 5
VTK_WORLD: int = 6
VTK_USERDEFINED: int = 7
@dataclass(frozen=True)
class Mapper:
@dataclass(frozen=True)
class ColorMode:
VTK_COLOR_MODE_DEFAULT: int = 0
VTK_COLOR_MODE_MAP_SCALARS: int = 1
VTK_COLOR_MODE_DIRECT_SCALARS: int = 2
@dataclass(frozen=True)
class ResolveCoincidentTopology:
VTK_RESOLVE_OFF: int = 0
VTK_RESOLVE_POLYGON_OFFSET: int = 1
VTK_RESOLVE_SHIFT_ZBUFFER: int = 2
@dataclass(frozen=True)
class ScalarMode:
VTK_SCALAR_MODE_DEFAULT: int = 0
VTK_SCALAR_MODE_USE_POINT_DATA: int = 1
VTK_SCALAR_MODE_USE_CELL_DATA: int = 2
VTK_SCALAR_MODE_USE_POINT_FIELD_DATA: int = 3
VTK_SCALAR_MODE_USE_CELL_FIELD_DATA: int = 4
VTK_SCALAR_MODE_USE_FIELD_DATA: int = 5
@dataclass(frozen=True)
class Property:
@dataclass(frozen=True)
class Interpolation:
VTK_FLAT: int = 0
VTK_GOURAUD: int = 1
VTK_PHONG: int = 2
VTK_PBR: int = 3
@dataclass(frozen=True)
class Representation:
VTK_POINTS: int = 0
VTK_WIREFRAME: int = 1
VTK_SURFACE: int = 2
@dataclass(frozen=True)
class TextProperty:
@dataclass(frozen=True)
class Justification:
VTK_TEXT_LEFT: int = 0
VTK_TEXT_CENTERED: int = 1
VTK_TEXT_RIGHT: int = 2
@dataclass(frozen=True)
class VerticalJustification:
VTK_TEXT_BOTTOM: int = 0
VTK_TEXT_CENTERED: int = 1
VTK_TEXT_TOP: int = 2
if __name__ == '__main__':
main()