PlatonicSolids
Repository source: PlatonicSolids
Description¶
Display all five Platonic solids in a grid.
Platonic solids are regular, convex polyhedrons. They are constructed by congruent (identical in shape and size) regular (all angles equal and all sides equal) polygonal faces with the same number of faces meeting at each vertex.
Five solids satisfy the above criteria:
| Figure | Tetrahedron | Cube | Octahedron | Icosahedron | Dodecahedron |
|---|---|---|---|---|---|
| Vertices | 4 | 8 | 6 (2 × 3) | 12 (4 × 3) | 20 (8 + 4 × 3) |
| Edges | 6 | 12 | 12 | 30 | 30 |
| Faces | 4 | 6 | 8 | 20 | 12 |
The relationship between vertices, edges and faces is given by Euler's formula:
V - E + F = 2
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
PlatonicSolids.py
#!/usr/bin/env python3
from collections import namedtuple
from dataclasses import dataclass
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingFreeType
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import (
vtkLookupTable,
vtkPoints
)
from vtkmodules.vtkCommonDataModel import (
vtkCellArray,
vtkPolyData,
vtkPolyLine
)
from vtkmodules.vtkFiltersSources import vtkPlatonicSolidSource
from vtkmodules.vtkInteractionWidgets import (
vtkTextRepresentation,
vtkTextWidget
)
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkActor2D,
vtkCoordinate,
vtkPolyDataMapper,
vtkPolyDataMapper2D,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkRenderer,
vtkTextActor,
vtkTextProperty
)
def main():
colors = vtkNamedColors()
name_orientation = get_name_orientation()
lut = get_platonic_lut()
# Set up the viewports.
x_grid_dimensions = 3
y_grid_dimensions = 2
renderer_size = 300
size = (x_grid_dimensions * renderer_size, y_grid_dimensions * renderer_size)
viewports = dict()
VP_Params = namedtuple('VP_Params', ['viewport', 'border'])
last_col = False
last_row = False
blank = len(name_orientation)
for row in range(0, y_grid_dimensions):
if row == y_grid_dimensions - 1:
last_row = True
for col in range(0, x_grid_dimensions):
if col == x_grid_dimensions - 1:
last_col = True
index = row * x_grid_dimensions + col
# (xmin, ymin, xmax, ymax)
viewport = (float(col) / x_grid_dimensions,
float(y_grid_dimensions - (row + 1)) / y_grid_dimensions,
float(col + 1) / x_grid_dimensions,
float(y_grid_dimensions - row) / y_grid_dimensions)
if last_row and last_col:
border = ViewPortBorders.tlbr
last_row = False
last_col = False
elif last_col:
border = ViewPortBorders.rtl
last_col = False
elif last_row:
border = ViewPortBorders.tlb
else:
border = ViewPortBorders.tl
vp_params = VP_Params(viewport, border)
if index < blank:
viewports[name_orientation[index].name] = vp_params
else:
viewports[index] = vp_params
# Create the render window and interactor.
ren_win = vtkRenderWindow(size=size, window_name='PlatonicSolids')
iren = vtkRenderWindowInteractor()
iren.render_window = ren_win
# Create one text property for all.
text_property = vtkTextProperty(color=colors.GetColor3d('AliceBlue'), bold=True, italic=True,
shadow=True, font_family_as_string='Courier',
font_size=16, justification=TextProperty.Justification.VTK_TEXT_CENTERED)
titles = list()
for n in name_orientation:
titles.append(n.name)
# Position text according to its length and centered in the viewport.
text_positions = get_text_positions(titles, justification=TextProperty.Justification.VTK_TEXT_CENTERED)
text_representations = list()
text_actors = list()
text_widgets = list()
renderers = list()
# Create and link the mappers actors and renderers together.
for i in range(0, len(name_orientation)):
viewport = viewports[name_orientation[i].name].viewport
border = viewports[name_orientation[i].name].border
renderer = vtkRenderer(background=colors.GetColor3d('SlateGray'), viewport=viewport)
draw_viewport_border(renderer, sides=border, border_color='Yellow', border_width=4)
platonic_solid = vtkPlatonicSolidSource(solid_type=i)
mapper = vtkPolyDataMapper(lookup_table=lut, scalar_range=(0, 19))
platonic_solid >> mapper
actor = vtkActor(mapper=mapper)
renderer.AddActor(actor)
# Create the text actor and representation.
text_actors.append(
vtkTextActor(input=name_orientation[i].name,
text_scale_mode=vtkTextActor.TEXT_SCALE_MODE_NONE,
text_property=text_property))
# Create the text representation. Used for positioning the text actor.
text_representations.append(vtkTextRepresentation(enforce_normalized_viewport_bounds=True))
text_representations[i].position_coordinate.value = text_positions[name_orientation[i].name]['p']
text_representations[i].position2_coordinate.value = text_positions[name_orientation[i].name]['p2']
# Create the text widget, setting the default renderer and interactor.
text_widgets.append(
vtkTextWidget(representation=text_representations[i], text_actor=text_actors[i],
default_renderer=renderer, interactor=iren, selectable=False))
# Orient the view.
renderer.ResetCamera()
renderer.active_camera.Azimuth(name_orientation[i].azimuth)
renderer.active_camera.Elevation(name_orientation[i].elevation)
renderer.active_camera.Zoom(name_orientation[i].zoom)
renderer.ResetCameraClippingRange()
renderers.append(renderer)
ren_win.AddRenderer(renderers[i])
for i in range(blank, x_grid_dimensions * y_grid_dimensions):
viewport = viewports[i].viewport
border = viewports[i].border
renderer = vtkRenderer(background=colors.GetColor3d('SlateGray'), viewport=viewport)
draw_viewport_border(renderer, sides=border, border_color='Yellow', border_width=4)
ren_win.AddRenderer(renderer)
for i in range(0, len(name_orientation)):
text_widgets[i].On()
iren.Initialize()
ren_win.Render()
iren.Start()
def get_name_orientation():
"""
Get the platonic solid names and initial orientations.
:return: The solids and their initial orientations.
"""
# [[name, azimuth, elevation, zoom] ...]
res = [['Tetrahedron', 45.0, 30.0, 1.0],
['Cube', -60.0, 45.0, 0.8],
['Octahedron', -15.0, 10.0, 1.0],
['Icosahedron', 4.5, 18.0, 1.0],
['Dodecahedron', 171.0, 22.0, 1.0]]
platonic_solids = namedtuple('platonic_solids', ('name', 'azimuth', 'elevation', 'zoom'))
# Convert res to a list of named tuples.
res = [platonic_solids(*row) for row in res]
return res
def get_platonic_lut():
"""
Get a specialised lookup table for the platonic solids.
Since each face of a vtkPlatonicSolidSource has a different
cell scalar, we create a lookup table with a different colour
for each face.
The colors have been carefully chosen so that adjacent cells
are colored distinctly.
:return: The lookup table.
"""
lut = vtkLookupTable(number_of_table_values=20, table_range=(0.0, 19.0))
# lut.SetNumberOfTableValues(20)
# lut.SetTableRange(0.0, 19.0)
lut.Build()
lut.SetTableValue(0, 0.1, 0.1, 0.1)
lut.SetTableValue(1, 0, 0, 1)
lut.SetTableValue(2, 0, 1, 0)
lut.SetTableValue(3, 0, 1, 1)
lut.SetTableValue(4, 1, 0, 0)
lut.SetTableValue(5, 1, 0, 1)
lut.SetTableValue(6, 1, 1, 0)
lut.SetTableValue(7, 0.9, 0.7, 0.9)
lut.SetTableValue(8, 0.5, 0.5, 0.5)
lut.SetTableValue(9, 0.0, 0.0, 0.7)
lut.SetTableValue(10, 0.5, 0.7, 0.5)
lut.SetTableValue(11, 0, 0.7, 0.7)
lut.SetTableValue(12, 0.7, 0, 0)
lut.SetTableValue(13, 0.7, 0, 0.7)
lut.SetTableValue(14, 0.7, 0.7, 0)
lut.SetTableValue(15, 0, 0, 0.4)
lut.SetTableValue(16, 0, 0.4, 0)
lut.SetTableValue(17, 0, 0.4, 0.4)
lut.SetTableValue(18, 0.4, 0, 0)
lut.SetTableValue(19, 0.4, 0, 0.4)
return lut
def get_text_positions(names, justification=0, vertical_justification=0, width=0.96, height=0.1):
"""
Get viewport positioning information for a list of names.
:param names: The list of names.
:param justification: Horizontal justification of the text, default is left.
:param vertical_justification: Vertical justification of the text, default is bottom.
:param width: Width of the bounding_box of the text in screen coordinates.
:param height: Height of the bounding_box of the text in screen coordinates.
:return: A list of positioning information.
"""
# The gap between the left or right edge of the screen and the text.
dx = 0.02
width = abs(width)
if width > 0.96:
width = 0.96
y0 = 0.01
height = abs(height)
if height > 0.9:
height = 0.9
dy = height
if vertical_justification == TextProperty.VerticalJustification.VTK_TEXT_TOP:
y0 = 1.0 - (dy + y0)
dy = height
if vertical_justification == TextProperty.VerticalJustification.VTK_TEXT_CENTERED:
y0 = 0.5 - (dy / 2.0 + y0)
dy = height
name_len_min = 0
name_len_max = 0
first = True
for k in names:
sz = len(k)
if first:
name_len_min = name_len_max = sz
first = False
else:
name_len_min = min(name_len_min, sz)
name_len_max = max(name_len_max, sz)
text_positions = dict()
for k in names:
sz = len(k)
delta_sz = width * sz / name_len_max
if delta_sz > width:
delta_sz = width
if justification == TextProperty.Justification.VTK_TEXT_CENTERED:
x0 = 0.5 - delta_sz / 2.0
elif justification == TextProperty.Justification.VTK_TEXT_RIGHT:
x0 = 1.0 - dx - delta_sz
else:
# Default is left justification.
x0 = dx
# For debugging!
# print(
# f'{k:16s}: (x0, y0) = ({x0:3.2f}, {y0:3.2f}), (x1, y1) = ({x0 + delta_sz:3.2f}, {y0 + dy:3.2f})'
# f', width={delta_sz:3.2f}, height={dy:3.2f}')
text_positions[k] = {'p': [x0, y0, 0], 'p2': [delta_sz, dy, 0]}
return text_positions
def draw_viewport_border(renderer, sides, border_color, border_width):
"""
Set a border around a viewport.
:param renderer: The renderer corresponding to the viewport.
:param sides: An array of boolean corresponding to [top, left, bottom, right]
:param border_color: The color of the border.
:param border_width: The width of the border.
:return:
"""
colors = vtkNamedColors()
# Points start at upper right and proceed anti-clockwise.
points = vtkPoints()
points.SetNumberOfPoints(4)
points.InsertPoint(0, 1, 1, 0)
points.InsertPoint(1, 0, 1, 0)
points.InsertPoint(2, 0, 0, 0)
points.InsertPoint(3, 1, 0, 0)
cells = vtkCellArray()
cells.Initialize()
if sides[0]:
# Top
top = vtkPolyLine()
top.GetPointIds().SetNumberOfIds(2)
top.GetPointIds().SetId(0, 0)
top.GetPointIds().SetId(1, 1)
cells.InsertNextCell(top)
if sides[1]:
# Left
left = vtkPolyLine()
left.GetPointIds().SetNumberOfIds(2)
left.GetPointIds().SetId(0, 1)
left.GetPointIds().SetId(1, 2)
cells.InsertNextCell(left)
if sides[2]:
# Bottom
bottom = vtkPolyLine()
bottom.GetPointIds().SetNumberOfIds(2)
bottom.GetPointIds().SetId(0, 2)
bottom.GetPointIds().SetId(1, 3)
cells.InsertNextCell(bottom)
if sides[3]:
# Right
right = vtkPolyLine()
right.GetPointIds().SetNumberOfIds(2)
right.GetPointIds().SetId(0, 3)
right.GetPointIds().SetId(1, 0)
cells.InsertNextCell(right)
# Now make the polydata and display it.
poly = vtkPolyData()
poly.Initialize()
poly.SetPoints(points)
poly.SetLines(cells)
# Use normalized viewport coordinates since
# they are independent of window size.
coordinate = vtkCoordinate()
coordinate.SetCoordinateSystemToNormalizedViewport()
mapper = vtkPolyDataMapper2D()
mapper.SetInputData(poly)
mapper.SetTransformCoordinate(coordinate)
actor = vtkActor2D()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(border_color))
# Line width should be at least 2 to be visible at extremes.
actor.GetProperty().SetLineWidth(border_width)
renderer.AddViewProp(actor)
@dataclass(frozen=True)
class ViewPortBorders:
# Define borders for the viewports = [top, left, bottom, right].
t = [True, False, False, False]
l = [False, True, False, False]
b = [False, False, True, False]
r = [False, False, False, True]
lb = [False, True, True, False]
lbr = [False, True, True, True]
tlb = [True, True, True, False]
tlbr = [True, True, True, True]
rtl = [True, True, False, True]
tl = [True, True, False, False]
@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 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()