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
# 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.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 = '''
'''
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('file_name', default=None, help='A polydata file e.g. Bunny.vtp')
args = parser.parse_args()
return args.file_name
def main():
colors = vtkNamedColors()
file_name = get_program_parameters()
fn = Path(file_name)
if not fn.is_file():
print(f'{fn}\nNot found.')
return
else:
poly_data = read_poly_data(file_name=fn)
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'numTetras: {num_tetras} numLines: {num_lines} numTris: {num_tris} numVerts: {num_verts}')
ss = f'numTetras: {num_tetras:3d}\nnumLines: {num_lines:3d}\nnumTris: {num_tris:3d}\nnumVerts: {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=(900, 300), 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)
# Setup a slider widget for each varying parameter.
slider_properties = SliderProperties()
# Setup a slider widget for each varying parameter.
slider_properties.title_text = 'Alpha'
slider_properties.range['maximum_value'] = 0.02
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['end_cap_width'] = slider_properties.dimensions['tube_width'] * 1.5
slider_properties.dimensions['label_height'] = 0.04
slider_properties.dimensions['title_height'] = 0.04
slider_properties.position = {'point1': (0.1, 0.1), 'point2': (0.9, 0.1)}
slider_widget_alpha = make_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()
class SliderProperties:
"""
These are default values.
"""
dimensions = {
'tube_width': 0.008,
'slider_length': 0.01, 'slider_width': 0.02,
'end_cap_length': 0.005, 'end_cap_width': 0.05,
'title_height': 0.03, 'label_height': 0.025,
}
colors = {
'title_color': 'White', 'label_color': 'White', 'slider_color': 'White',
'selected_color': 'HotPink', 'bar_color': 'White', 'bar_ends_color': 'White',
}
range = {'minimum_value': 0.0, 'maximum_value': 1.0, 'value': 0.0}
title_text = '',
position = {'point1': (0.1, 0.1), 'point2': (0.9, 0.1)}
def make_slider_widget(slider_properties, interactor):
"""
Make a slider widget.
:param slider_properties: range, title name, dimensions, colors, and position.
:param interactor: The vtkInteractor.
:return: The slider widget.
"""
colors = vtkNamedColors()
slider_rep = vtkSliderRepresentation2D(minimum_value=slider_properties.range['minimum_value'],
maximum_value=slider_properties.range['maximum_value'],
value=slider_properties.range['value'],
title_text=slider_properties.title_text,
tube_width=slider_properties.dimensions['tube_width'],
slider_length=slider_properties.dimensions['slider_length'],
slider_width=slider_properties.dimensions['slider_width'],
end_cap_length=slider_properties.dimensions['end_cap_length'],
end_cap_width=slider_properties.dimensions['end_cap_width'],
title_height=slider_properties.dimensions['title_height'],
label_height=slider_properties.dimensions['label_height'],
)
# Set the color properties.
slider_rep.title_property.color = colors.GetColor3d(slider_properties.colors['title_color'])
slider_rep.label_property.color = colors.GetColor3d(slider_properties.colors['label_color'])
slider_rep.tube_property.color = colors.GetColor3d(slider_properties.colors['bar_color'])
slider_rep.cap_property.color = colors.GetColor3d(slider_properties.colors['bar_ends_color'])
slider_rep.slider_property.color = colors.GetColor3d(slider_properties.colors['slider_color'])
slider_rep.selected_property.color = colors.GetColor3d(slider_properties.colors['selected_color'])
# Set the position.
slider_rep.point1_coordinate.coordinate_system = Coordinate.CoordinateSystem.VTK_NORMALIZED_VIEWPORT
slider_rep.point1_coordinate.value = slider_properties.position['point1']
slider_rep.point2_coordinate.coordinate_system = Coordinate.CoordinateSystem.VTK_NORMALIZED_VIEWPORT
slider_rep.point2_coordinate.value = slider_properties.position['point2']
widget = vtkSliderWidget(representation=slider_rep, interactor=interactor, enabled=True)
widget.SetAnimationModeToAnimate()
widget.number_of_animation_steps = 2
return widget
# 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'numTetras: {num_tetras:3d}\nnumLines: {num_lines:3d}\nnumTris: {num_tris:3d}\nnumVerts: {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()