Skip to content

LinearCellsDemo

Repository source: LinearCellsDemo

Description

Linear cell types found in VTK.

The numbers define the ordering of the defining points.

Options are provided to show a wire frame (-w) or to add a back face color (-b). You can also remove the plinth with the (-n) option. If you want a single object, use -o followed by the object number.

With the back face option selected, the back face color will be visible as the objects are semitransparent.

Other languages

See (Cxx), (Python)

Question

If you have a question about this example, please use the VTK Discourse Forum

Code

LinearCellsDemo.py

# !/usr/bin/env python3

from collections import namedtuple
from dataclasses import dataclass

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import vtkPoints
from vtkmodules.vtkCommonDataModel import (
    VTK_TETRA,
    vtkPolyData,
    vtkCellArray,
    vtkHexagonalPrism,
    vtkHexahedron,
    vtkLine,
    vtkPentagonalPrism,
    vtkPixel,
    vtkPolyLine,
    vtkPolyVertex,
    vtkPolygon,
    vtkPyramid,
    vtkQuad,
    vtkTetra,
    vtkTriangle,
    vtkTriangleStrip,
    vtkUnstructuredGrid,
    vtkVertex,
    vtkVoxel,
    vtkWedge
)
# noinspection PyUnresolvedReferences
from vtkmodules.vtkCommonTransforms import vtkTransform
from vtkmodules.vtkFiltersCore import vtkAppendPolyData
# noinspection PyUnresolvedReferences
from vtkmodules.vtkFiltersGeneral import vtkTransformFilter
from vtkmodules.vtkFiltersSources import (
    vtkCubeSource,
    vtkSphereSource
)
from vtkmodules.vtkInteractionWidgets import vtkCameraOrientationWidget
from vtkmodules.vtkInteractionWidgets import (
    vtkTextRepresentation,
    vtkTextWidget
)
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkActor2D,
    vtkCoordinate,
    vtkDataSetMapper,
    vtkGlyph3DMapper,
    vtkLightKit,
    vtkPolyDataMapper,
    vtkPolyDataMapper2D,
    vtkProperty,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer,
    vtkTextActor,
    vtkTextProperty,
)
from vtkmodules.vtkRenderingLabel import vtkLabeledDataMapper


def get_program_parameters():
    import argparse
    description = 'Demonstrate the linear cell types found in VTK.'
    epilogue = '''
     The numbers define the ordering of the points making the cell.
    '''
    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    group1 = parser.add_mutually_exclusive_group()
    group1.add_argument('-w', '--wireframe', action='store_true',
                        help='Render a wireframe.')
    group1.add_argument('-b', '--backface', action='store_true',
                        help='Display the back face in a different colour.')

    parser.add_argument('-o', '--object_number', type=int, default=None,
                        help='The number corresponding to the object.')
    parser.add_argument('-n', '--no_plinth', action='store_true',
                        help='Remove the plinth.')
    args = parser.parse_args()
    return args.wireframe, args.backface, args.object_number, args.no_plinth


def main():
    wireframe_on, backface_on, object_num, plinth_off = get_program_parameters()

    objects = specify_objects()
    # The order here should match the order in specify_objects().
    object_order = list(objects.keys())

    # Check for a single object.
    single_object = None
    if object_num:
        if object_num in object_order:
            single_object = True
        else:
            print('Object not found.\nPlease enter the number corresponding to the object.')
            print('Available objects are:')
            for obj in object_order:
                print(f'{objects[obj]} (={str(obj)})')
            return

    colors = vtkNamedColors()

    # Create one sphere for all.
    sphere = vtkSphereSource(phi_resolution=21, theta_resolution=21, radius=0.04)

    cells = get_unstructured_grids()
    # The text to be displayed in the viewport.
    names = list()
    # The keys of the objects selected for display.
    keys = list()
    if single_object:
        names.append(f'{objects[object_num]} (={str(object_num)})')
        keys.append(object_num)
    else:
        for obj in object_order:
            names.append(f'{objects[obj]} (={str(obj)})')
            keys.append(obj)

    add_plinth = (10, 11, 12, 13, 14, 15, 16,)
    lines = (3, 4)

    # Set up the viewports.
    grid_column_dimensions = 4
    grid_row_dimensions = 4
    renderer_size = 300
    if single_object:
        grid_column_dimensions = 1
        grid_row_dimensions = 1
        renderer_size = 1200
    window_size = (grid_column_dimensions * renderer_size, grid_row_dimensions * renderer_size)

    viewports = dict()
    VP_Params = namedtuple('VP_Params', ['viewport', 'border'])
    last_col = False
    last_row = False
    blank = len(cells)
    blank_viewports = list()

    for row in range(0, grid_row_dimensions):
        if row == grid_row_dimensions - 1:
            last_row = True
        for col in range(0, grid_column_dimensions):
            if col == grid_column_dimensions - 1:
                last_col = True
            index = row * grid_column_dimensions + col
            # Set the renderer's viewport dimensions (xmin, ymin, xmax, ymax) within the render window.
            # Note that for the Y values, we need to subtract the row index from grid_rows
            #  because the viewport Y axis points upwards, and we want to draw the grid from top to down.
            viewport = (float(col) / grid_column_dimensions,
                        float(grid_row_dimensions - (row + 1)) / grid_row_dimensions,
                        float(col + 1) / grid_column_dimensions,
                        float(grid_row_dimensions - row) / grid_row_dimensions)

            if last_row and last_col:
                border = ViewPort.Border.TOP_LEFT_BOTTOM_RIGHT
                last_row = False
                last_col = False
            elif last_col:
                border = ViewPort.Border.RIGHT_TOP_LEFT
                last_col = False
            elif last_row:
                border = ViewPort.Border.TOP_LEFT_BOTTOM
            else:
                border = ViewPort.Border.TOP_LEFT
            vp_params = VP_Params(viewport, border)
            if index < blank:
                viewports[keys[index]] = vp_params
            else:
                s = f'vp_{col:d}_{row:d}'
                viewports[s] = vp_params
                blank_viewports.append(s)

    # Position text according to its length and centered in the viewport.
    text_positions = get_text_positions(names,
                                        justification=TextProperty.Justification.VTK_TEXT_CENTERED,
                                        vertical_justification=TextProperty.VerticalJustification.VTK_TEXT_BOTTOM,
                                        width=0.85, height=0.1)

    ren_win = vtkRenderWindow(size=window_size, window_name='LinearCellDemo')
    ren_win.SetWindowName('LinearCellsDemo')

    iren = vtkRenderWindowInteractor()
    iren.render_window = ren_win
    # Since we always import vtkmodules.vtkInteractionStyle we can do this
    # because vtkInteractorStyleSwitch is automatically imported:
    iren.interactor_style.SetCurrentStyleToTrackballCamera()

    renderers = dict()
    text_representations = list()
    text_actors = list()
    text_widgets = list()

    # Create and link the mappers, actors and renderers together.
    single_object_key = None
    for idx, key in enumerate(keys):
        print('Creating:', names[idx])

        if single_object:
            single_object_key = key

        mapper = vtkDataSetMapper()
        cells[key][0] >> mapper
        actor = vtkActor(mapper=mapper, property=get_actor_property())
        if wireframe_on or key in lines:
            actor.property.representation = Property.Representation.VTK_WIREFRAME
            actor.property.line_width = 2
            actor.property.opacity = 1
            actor.property.color = colors.GetColor3d('Black')
        else:
            if backface_on:
                actor.backface_property = get_back_face_property()

        # Label the points.
        label_property = get_label_property()
        if single_object:
            label_property.SetFontSize(renderer_size // 36)
        else:
            label_property.SetFontSize(renderer_size // 16)

        label_mapper = vtkLabeledDataMapper(label_text_property=label_property)
        cells[key][0] >> label_mapper
        label_actor = vtkActor2D(mapper=label_mapper)

        # Glyph the points.
        point_mapper = vtkGlyph3DMapper(scaling=True, scalar_visibility=False,
                                        source_connection=sphere.output_port)
        cells[key][0] >> point_mapper
        point_actor = vtkActor(mapper=point_mapper, property=get_point_actor_property())

        viewport = viewports[key].viewport
        border = viewports[key].border
        renderer = vtkRenderer(background=colors.GetColor3d('LightSteelBlue'), viewport=viewport)
        draw_viewport_border(renderer, border=border, color=colors.GetColor3d('MidnightBlue'), line_width=4)

        light_kit = vtkLightKit()
        light_kit.AddLightsToRenderer(renderer)

        renderer.AddActor(actor)
        renderer.AddActor(label_actor)
        renderer.AddActor(point_actor)
        if not plinth_off:
            # Add a plinth.
            if key in add_plinth:
                tile_actor = make_tile(cells[key][0].GetBounds(),
                                       expansion_factor=0.5, thickness_ratio=0.01, shift_y=-0.05)
                tile_actor.property = get_tile_property()
                renderer.AddActor(tile_actor)

        # Create the text actor and representation.
        text_property = get_text_property()
        if single_object:
            single_object_key = key

        text_property = get_text_property()
        if single_object:
            text_property.SetFontSize(renderer_size // 28)
        else:
            text_property.SetFontSize(renderer_size // 24)

        text_actor = vtkTextActor(input=names[idx],
                                  text_scale_mode=vtkTextActor.TEXT_SCALE_MODE_NONE,
                                  text_property=text_property)

        # Create the text representation. Used for positioning the text actor.
        text_representation = vtkTextRepresentation(enforce_normalized_viewport_bounds=True)
        text_representation.GetPositionCoordinate().value = text_positions[names[idx]]['p']
        text_representation.GetPosition2Coordinate().value = text_positions[names[idx]]['p2']

        # Create the text widget, setting the default renderer and interactor.
        text_widget = vtkTextWidget(representation=text_representation, text_actor=text_actor,
                                    default_renderer=renderer, interactor=iren, selectable=False)

        text_actors.append(text_actor)
        text_representations.append(text_representation)
        text_widgets.append(text_widget)

        renderer.ResetCamera()
        renderer.active_camera.Azimuth(cells[key][1].azimuth)
        renderer.active_camera.Elevation(cells[key][1].elevation)
        renderer.active_camera.Dolly(cells[key][1].zoom)
        renderer.ResetCameraClippingRange()

        renderers[key] = renderer
        ren_win.AddRenderer(renderers[key])

    for name in blank_viewports:
        viewport = viewports[name].viewport
        border = viewports[name].border
        renderer = vtkRenderer(background=colors.GetColor3d('LightSteelBlue'), viewport=viewport)
        draw_viewport_border(renderer, border=border, color=colors.GetColor3d('MidnightBlue'), line_width=4)

        renderers[name] = renderer
        ren_win.AddRenderer(renderers[name])

    for i in range(0, len(text_widgets)):
        text_widgets[i].On()

    if single_object:
        cam_orient_manipulator = vtkCameraOrientationWidget(parent_renderer=renderers[single_object_key],
                                                            interactor=iren)
        cam_orient_manipulator.On()

    ren_win.Render()
    iren.Initialize()
    iren.Start()


def specify_objects():
    """
    Link the unstructured grid number to the unstructured grid name.

    :return: A dictionary: {index number: unstructured grid name}.
    """
    return {
        1: 'VTK_VERTEX',
        2: 'VTK_POLY_VERTEX',
        3: 'VTK_LINE',
        4: 'VTK_POLY_LINE',
        5: 'VTK_TRIANGLE',
        6: 'VTK_TRIANGLE_STRIP',
        7: 'VTK_POLYGON',
        8: 'VTK_PIXEL',
        9: 'VTK_QUAD',
        10: 'VTK_TETRA',
        11: 'VTK_VOXEL',
        12: 'VTK_HEXAHEDRON',
        13: 'VTK_WEDGE',
        14: 'VTK_PYRAMID',
        15: 'VTK_PENTAGONAL_PRISM',
        16: 'VTK_HEXAGONAL_PRISM',
    }


def get_unstructured_grids():
    """
    Get the unstructured grid names, the unstructured grid and initial orientations.

    :return: A dictionary: {index number: (unstructured grid, orientation)}.
    """

    def make_orientation(azimuth: float = 0, elevation: float = 0, zoom: float = 1.0):
        return Orientation(azimuth, elevation, zoom)

    return {
        1: (make_vertex(), make_orientation(30, -30, 0.1)),
        2: (make_poly_vertex(), make_orientation(30, -30, 0.8)),
        3: (make_line(), make_orientation(30, -30, 0.4)),
        4: (make_polyline(), make_orientation(30, -30, 1.0)),
        5: (make_triangle(), make_orientation(30, -30, 0.7)),
        6: (make_triangle_strip(), make_orientation(30, -30, 1.1)),
        7: (make_polygon(), make_orientation(0, -45, 1.0)),
        8: (make_pixel(), make_orientation(0, -45, 1.0)),
        9: (make_quad(), make_orientation(0, -45, 0)),
        10: (make_tetra(), make_orientation(20, 20, 1.0)),
        11: (make_voxel(), make_orientation(-22.5, 15, 0.95)),
        12: (make_hexahedron(), make_orientation(-22.5, 15, 0.95)),
        13: (make_wedge(), make_orientation(-30, 15, 1.0)),
        14: (make_pyramid(), make_orientation(-60, 15, 1.0)),
        15: (make_pentagonal_prism(), make_orientation(-60, 10, 1.0)),
        16: (make_hexagonal_prism(), make_orientation(-60, 15, 1.0)),
    }


@dataclass(frozen=True)
class Orientation:
    azimuth: float
    elevation: float
    zoom: float


# These functions return a vtkUnstructured grid corresponding to the object.

def make_vertex():
    # A vertex is a cell that represents a 3D point
    number_of_vertices = 1

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)

    vertex = vtkVertex()
    for i in range(0, number_of_vertices):
        vertex.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(vertex.GetCellType(), vertex.GetPointIds())

    return ug


def make_poly_vertex():
    # A polyvertex is a cell representing a set of 0D vertices
    number_of_vertices = 6

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(1, 0, 0)
    points.InsertNextPoint(0, 1, 0)
    points.InsertNextPoint(0, 0, 1)
    points.InsertNextPoint(1, 0, 0.4)
    points.InsertNextPoint(0, 1, 0.6)

    poly_vertex = vtkPolyVertex()
    poly_vertex.point_ids.SetNumberOfIds(number_of_vertices)

    for i in range(0, number_of_vertices):
        poly_vertex.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(poly_vertex.GetCellType(), poly_vertex.GetPointIds())

    return ug


def make_line():
    # A line is a cell that represents a 1D point.
    number_of_vertices = 2

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(0.5, 0.5, 0)

    line = vtkLine()
    for i in range(0, number_of_vertices):
        line.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(line.GetCellType(), line.GetPointIds())

    return ug


def make_polyline():
    # A polyline is a cell that represents a set of 1D lines.
    number_of_vertices = 5

    points = vtkPoints()
    points.InsertNextPoint(0, 0.5, 0)
    points.InsertNextPoint(0.5, 0, 0)
    points.InsertNextPoint(1, 0.3, 0)
    points.InsertNextPoint(1.5, 0.4, 0)
    points.InsertNextPoint(2.0, 0.4, 0)

    polyline = vtkPolyLine()
    polyline.point_ids.SetNumberOfIds(number_of_vertices)

    for i in range(0, number_of_vertices):
        polyline.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(polyline.GetCellType(), polyline.GetPointIds())

    return ug


def make_triangle():
    # A triangle is a cell that represents a triangle.
    number_of_vertices = 3

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(0.5, 0.5, 0)
    points.InsertNextPoint(.2, 1, 0)

    triangle = vtkTriangle()
    for i in range(0, number_of_vertices):
        triangle.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(triangle.GetCellType(), triangle.GetPointIds())

    return ug


def make_triangle_strip():
    # A triangle is a cell that represents a triangle strip.
    number_of_vertices = 10

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(1, -.1, 0)
    points.InsertNextPoint(0.5, 1, 0)
    points.InsertNextPoint(2.0, -0.1, 0)
    points.InsertNextPoint(1.5, 0.8, 0)
    points.InsertNextPoint(3.0, 0, 0)
    points.InsertNextPoint(2.5, 0.9, 0)
    points.InsertNextPoint(4.0, -0.2, 0)
    points.InsertNextPoint(3.5, 0.8, 0)
    points.InsertNextPoint(4.5, 1.1, 0)

    triangle_strip = vtkTriangleStrip()
    triangle_strip.point_ids.SetNumberOfIds(number_of_vertices)
    for i in range(0, number_of_vertices):
        triangle_strip.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(triangle_strip.GetCellType(), triangle_strip.GetPointIds())

    return ug


def make_polygon():
    # A polygon is a cell that represents a polygon.
    number_of_vertices = 6

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(1, -0.1, 0)
    points.InsertNextPoint(0.8, 0.5, 0)
    points.InsertNextPoint(1, 1, 0)
    points.InsertNextPoint(0.6, 1.2, 0)
    points.InsertNextPoint(0, 0.8, 0)

    polygon = vtkPolygon()
    polygon.point_ids.SetNumberOfIds(number_of_vertices)
    for i in range(0, number_of_vertices):
        polygon.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(polygon.GetCellType(), polygon.GetPointIds())

    return ug


def make_pixel():
    # A pixel is a cell that represents a pixel.
    number_of_vertices = 4

    pixel = vtkPixel()
    pixel.points.SetPoint(0, 0, 0, 0)
    pixel.points.SetPoint(1, 1, 0, 0)
    pixel.points.SetPoint(2, 0, 1, 0)
    pixel.points.SetPoint(3, 1, 1, 0)

    for i in range(0, number_of_vertices):
        pixel.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=pixel.points)
    ug.InsertNextCell(pixel.GetCellType(), pixel.GetPointIds())

    return ug


def make_quad():
    # A quad is a cell that represents a quad.
    number_of_vertices = 4

    quad = vtkQuad()
    quad.points.SetPoint(0, 0, 0, 0)
    quad.points.SetPoint(1, 1, 0, 0)
    quad.points.SetPoint(2, 1, 1, 0)
    quad.points.SetPoint(3, 0, 1, 0)

    for i in range(0, number_of_vertices):
        quad.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=quad.points)
    ug.InsertNextCell(quad.cell_type, quad.point_ids)

    return ug


def make_tetra():
    number_of_vertices = 4
    # Make a tetrahedron.

    points = vtkPoints()
    # points.InsertNextPoint(0, 0, 0)
    # points.InsertNextPoint(1, 0, 0)
    # points.InsertNextPoint(1, 1, 0)
    # points.InsertNextPoint(0, 1, 1)

    # Rotate the above points -90° about the X-axis.
    points.InsertNextPoint((0.0, 0.0, 0.0))
    points.InsertNextPoint((1.0, 0.0, 0.0))
    points.InsertNextPoint((1.0, 0.0, -1.0))
    points.InsertNextPoint((0.0, 1.0, -1.0))

    tetra = vtkTetra()
    for i in range(0, number_of_vertices):
        tetra.point_ids.SetId(i, i)

    cell_array = vtkCellArray()
    cell_array.InsertNextCell(tetra)

    ug = vtkUnstructuredGrid(points=points)
    ug.SetCells(VTK_TETRA, cell_array)

    # pd = vtkPolyData(points=points)
    # t = vtkTransform()
    # t.RotateX(-90)
    # t.Translate(0, 0, 0)
    # tf = vtkTransformFilter(transform=t)
    # (pd >> tf).update()
    # pts = tf.output.GetPoints()
    # for i in range(0, pts.number_of_points):
    #     print(f'points.InsertNextPoint({pts.GetPoint(i)})')

    return ug


def make_voxel():
    # A voxel is a representation of a regular grid in 3-D space.
    number_of_vertices = 8

    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(1, 0, 0)
    points.InsertNextPoint(0, 1, 0)
    points.InsertNextPoint(1, 1, 0)
    points.InsertNextPoint(0, 0, 1)
    points.InsertNextPoint(1, 0, 1)
    points.InsertNextPoint(0, 1, 1)
    points.InsertNextPoint(1, 1, 1)

    voxel = vtkVoxel()
    for i in range(0, number_of_vertices):
        voxel.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(voxel.cell_type, voxel.point_ids)

    return ug


def make_hexahedron():
    """
    A regular hexagon (cube) with all faces square and three squares
     around each vertex is created below.

    Set up the coordinates of eight points, (the two faces must be
     in counter-clockwise order as viewed from the outside).

    :return:
    """

    number_of_vertices = 8

    # Create the points.
    points = vtkPoints()
    points.InsertNextPoint(0, 0, 0)
    points.InsertNextPoint(1, 0, 0)
    points.InsertNextPoint(1, 1, 0)
    points.InsertNextPoint(0, 1, 0)
    points.InsertNextPoint(0, 0, 1)
    points.InsertNextPoint(1, 0, 1)
    points.InsertNextPoint(1, 1, 1)
    points.InsertNextPoint(0, 1, 1)

    # Create a hexahedron from the points.
    hexhedr = vtkHexahedron()
    for i in range(0, number_of_vertices):
        hexhedr.point_ids.SetId(i, i)

    # Add the points and hexahedron to an unstructured grid
    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(hexhedr.cell_type, hexhedr.point_ids)

    return ug


def make_wedge():
    number_of_vertices = 6
    # A wedge consists of two triangular ends and three rectangular faces.

    points = vtkPoints()

    # Original Points.
    # points.InsertNextPoint(0, 1, 0)
    # points.InsertNextPoint(0, 0, 0)
    # points.InsertNextPoint(0, 0.5, 0.5)
    # points.InsertNextPoint(1, 1, 0)
    # points.InsertNextPoint(1, 0.0, 0.0)
    # points.InsertNextPoint(1, 0.5, 0.5)

    # Rotate the above points -90° about the X-axis
    # and translate -1 along the Y-axis.
    points.InsertNextPoint(0.0, 0.0, 0.0)
    points.InsertNextPoint(0.0, 0, 1.0)
    points.InsertNextPoint(0.0, 0.5, 0.5)
    points.InsertNextPoint(1.0, 0.0, 0.0)
    points.InsertNextPoint(1.0, 0, 1.0)
    points.InsertNextPoint(1.0, 0.5, 0.5)

    wedge = vtkWedge()
    for i in range(0, number_of_vertices):
        wedge.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(wedge.cell_type, wedge.point_ids)

    # pd = vtkPolyData(points=points)
    # t = vtkTransform()
    # t.RotateX(-90)
    # t.Translate(0,-1,0)
    # tf = vtkTransformFilter(transform=t)
    # (pd >> tf).update()
    # pts = tf.output.GetPoints()
    # for i in range(0, pts.number_of_points):
    #     print(f'points.InsertNextPoint{pts.GetPoint(i)}')

    return ug


def make_pyramid():
    # Make a regular square pyramid.
    number_of_vertices = 5

    points = vtkPoints()

    # Original points.
    # p0 = [1.0, 1.0, 0.0]
    # p1 = [-1.0, 1.0, 0.0]
    # p2 = [-1.0, -1.0, 0.0]
    # p3 = [1.0, -1.0, 0.0]
    # p4 = [0.0, 0.0, 1.0]

    # Rotate the above points -90° about the X-axis.
    p0 = (1.0, 0, -1.0)
    p1 = (-1.0, 0, -1.0)
    p2 = (-1.0, 0, 1.0)
    p3 = (1.0, 0, 1.0)
    p4 = (0.0, 2.0, 0)

    points.InsertNextPoint(p0)
    points.InsertNextPoint(p1)
    points.InsertNextPoint(p2)
    points.InsertNextPoint(p3)
    points.InsertNextPoint(p4)

    pyramid = vtkPyramid()
    for i in range(0, number_of_vertices):
        pyramid.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=points)
    ug.InsertNextCell(pyramid.cell_type, pyramid.point_ids)

    # pd = vtkPolyData(points=points)
    # t = vtkTransform()
    # t.RotateX(-90)
    # t.Translate(0,0,0)
    # tf = vtkTransformFilter(transform=t)
    # (pd >> tf).update()
    # pts = tf.output.GetPoints()
    # for i in range(0, pts.number_of_points):
    #     print(f'p{i} = {pts.GetPoint(i)}')

    return ug


def make_pentagonal_prism():
    number_of_vertices = 10

    pentagonal_prism = vtkPentagonalPrism()

    scale = 2.0
    pentagonal_prism.points.SetPoint(0, 11 / scale, 10 / scale, 10 / scale)
    pentagonal_prism.points.SetPoint(1, 13 / scale, 10 / scale, 10 / scale)
    pentagonal_prism.points.SetPoint(2, 14 / scale, 12 / scale, 10 / scale)
    pentagonal_prism.points.SetPoint(3, 12 / scale, 14 / scale, 10 / scale)
    pentagonal_prism.points.SetPoint(4, 10 / scale, 12 / scale, 10 / scale)
    pentagonal_prism.points.SetPoint(5, 11 / scale, 10 / scale, 14 / scale)
    pentagonal_prism.points.SetPoint(6, 13 / scale, 10 / scale, 14 / scale)
    pentagonal_prism.points.SetPoint(7, 14 / scale, 12 / scale, 14 / scale)
    pentagonal_prism.points.SetPoint(8, 12 / scale, 14 / scale, 14 / scale)
    pentagonal_prism.points.SetPoint(9, 10 / scale, 12 / scale, 14 / scale)

    for i in range(0, number_of_vertices):
        pentagonal_prism.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=pentagonal_prism.points)
    ug.InsertNextCell(pentagonal_prism.cell_type, pentagonal_prism.point_ids)

    return ug


def make_hexagonal_prism():
    number_of_vertices = 12

    hexagonal_prism = vtkHexagonalPrism()

    scale = 2.0
    hexagonal_prism.points.SetPoint(0, 11 / scale, 10 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(1, 13 / scale, 10 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(2, 14 / scale, 12 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(3, 13 / scale, 14 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(4, 11 / scale, 14 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(5, 10 / scale, 12 / scale, 10 / scale)
    hexagonal_prism.points.SetPoint(6, 11 / scale, 10 / scale, 14 / scale)
    hexagonal_prism.points.SetPoint(7, 13 / scale, 10 / scale, 14 / scale)
    hexagonal_prism.points.SetPoint(8, 14 / scale, 12 / scale, 14 / scale)
    hexagonal_prism.points.SetPoint(9, 13 / scale, 14 / scale, 14 / scale)
    hexagonal_prism.points.SetPoint(10, 11 / scale, 14 / scale, 14 / scale)
    hexagonal_prism.points.SetPoint(11, 10 / scale, 12 / scale, 14 / scale)

    for i in range(0, number_of_vertices):
        hexagonal_prism.point_ids.SetId(i, i)

    ug = vtkUnstructuredGrid(points=hexagonal_prism.points)
    ug.InsertNextCell(hexagonal_prism.cell_type, hexagonal_prism.point_ids)

    return ug


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, border, color=(0, 0, 0), line_width=2):
    """
    Draw a border around the viewport of a renderer.

    :param renderer: The renderer.
    :param border: The border to draw, it must be one of the constants in ViewPort.Border.
    :param color: The color.
    :param line_width: The line width of the border.
    :return:
    """

    def generate_border_lines(border_type):
        """
        Generate the lines for the border.

        :param border_type:  The border type to draw, it must be one of the constants in ViewPort.Border
        :return: The points and lines.
        """
        if border_type >= ViewPort.Border.NUMBER_OF_BORDER_TYPES:
            print('Not a valid border type.')
            return None

        # Points start at upper right and proceed anti-clockwise.
        pts = (
            (1, 1, 0),
            (0, 1, 0),
            (0, 0, 0),
            (1, 0, 0),
            (1, 1, 0),
        )
        pt_orders = {
            ViewPort.Border.TOP: (0, 1),
            ViewPort.Border.LEFT: (1, 2),
            ViewPort.Border.BOTTOM: (2, 3),
            ViewPort.Border.RIGHT: (3, 4),
            ViewPort.Border.LEFT_BOTTOM: (1, 2, 3),
            ViewPort.Border.BOTTOM_RIGHT: (2, 3, 4),
            ViewPort.Border.RIGHT_TOP: (3, 4, 1),
            ViewPort.Border.RIGHT_TOP_LEFT: (3, 4, 1, 2),
            ViewPort.Border.TOP_LEFT: (0, 1, 2),
            ViewPort.Border.TOP_LEFT_BOTTOM: (0, 1, 2, 3),
            ViewPort.Border.TOP_LEFT_BOTTOM_RIGHT: (0, 1, 2, 3, 4)
        }
        pt_order = pt_orders[border_type]
        number_of_points = len(pt_order)
        points = vtkPoints(number_of_points=number_of_points)
        i = 0
        for pt_id in pt_order:
            points.InsertPoint(i, *pts[pt_id])
            i += 1

        lines = vtkPolyLine()
        lines.point_ids.SetNumberOfIds(number_of_points)
        for i in range(0, number_of_points):
            lines.point_ids.id = (i, i)

        cells = vtkCellArray()
        cells.InsertNextCell(lines)

        # Make the polydata and return.
        return vtkPolyData(points=points, lines=cells)

    # Use normalized viewport coordinates since
    # they are independent of window size.
    coordinate = vtkCoordinate(coordinate_system=Coordinate.CoordinateSystem.VTK_NORMALIZED_VIEWPORT)
    poly = vtkAppendPolyData()
    if border == ViewPort.Border.TOP_BOTTOM:
        (
            generate_border_lines(ViewPort.Border.TOP),
            generate_border_lines(ViewPort.Border.BOTTOM)
        ) >> poly
    elif border == ViewPort.Border.LEFT_RIGHT:
        (
            generate_border_lines(ViewPort.Border.LEFT),
            generate_border_lines(ViewPort.Border.RIGHT)
        ) >> poly
    else:
        generate_border_lines(border) >> poly

    mapper = vtkPolyDataMapper2D(transform_coordinate=coordinate)
    poly >> mapper
    actor = vtkActor2D(mapper=mapper)
    actor.property.color = color
    # Line width should be at least 2 to be visible at the extremes.
    actor.property.line_width = line_width

    renderer.AddViewProp(actor)


def make_tile(bounds, expansion_factor=0.5, thickness_ratio=0.05, shift_y=-0.05):
    """
    Make a tile slightly larger or smaller than the bounds in the
      X and Z directions and thinner or thicker in the Y direction.

    A thickness_ratio of zero reduces the tile to an XZ plane.

    :param bounds: The bounds for the tile.
    :param expansion_factor: The expansion factor in the XZ plane.
    :param thickness_ratio: The thickness ratio in the Y direction, >= 0.
    :param shift_y: Used to shift the centre of the plinth in the Y-direction.
    :return: An actor corresponding to the tile.
    """

    d_xyz = (
        bounds[1] - bounds[0],
        bounds[3] - bounds[2],
        bounds[5] - bounds[4]
    )
    thickness = d_xyz[2] * abs(thickness_ratio)
    center = ((bounds[1] + bounds[0]) / 2.0,
              bounds[2] - thickness / 2.0 + shift_y,
              (bounds[5] + bounds[4]) / 2.0)
    x_length = bounds[1] - bounds[0] + (d_xyz[0] * expansion_factor)
    z_length = bounds[5] - bounds[4] + (d_xyz[2] * expansion_factor)

    plane = vtkCubeSource(center=center, x_length=x_length, y_length=thickness, z_length=z_length)

    plane_mapper = vtkPolyDataMapper()
    plane >> plane_mapper

    return vtkActor(mapper=plane_mapper)


def get_text_property():
    colors = vtkNamedColors()

    return vtkTextProperty(color=colors.GetColor3d('Black'),
                           bold=True, italic=False, shadow=False,
                           font_family_as_string='Courier',
                           justification=TextProperty.Justification.VTK_TEXT_CENTERED)


def get_label_property():
    colors = vtkNamedColors()

    return vtkTextProperty(color=colors.GetColor3d('DeepPink'),
                           bold=True, italic=False, shadow=True,
                           font_family_as_string='Courier',
                           justification=TextProperty.Justification.VTK_TEXT_CENTERED)


def get_back_face_property():
    colors = vtkNamedColors()

    return vtkProperty(
        ambient_color=colors.GetColor3d('LightSalmon'),
        diffuse_color=colors.GetColor3d('OrangeRed'),
        specular_color=colors.GetColor3d('White'),
        specular=0.2, diffuse=1.0, ambient=0.2, specular_power=20.0,
        opacity=1.0)


def get_actor_property():
    colors = vtkNamedColors()

    return vtkProperty(
        ambient_color=colors.GetColor3d('DarkSalmon'),
        diffuse_color=colors.GetColor3d('Seashell'),
        specular_color=colors.GetColor3d('White'),
        specular=0.5, diffuse=0.7, ambient=0.5, specular_power=20.0,
        opacity=0.9, edge_visibility=True, line_width=3)


def get_point_actor_property():
    colors = vtkNamedColors()

    return vtkProperty(
        ambient_color=colors.GetColor3d('Gold'),
        diffuse_color=colors.GetColor3d('Yellow'),
        specular_color=colors.GetColor3d('White'),
        specular=0.5, diffuse=0.7, ambient=0.5, specular_power=20.0,
        opacity=1.0)


def get_tile_property():
    colors = vtkNamedColors()

    return vtkProperty(
        ambient_color=colors.GetColor3d('SteelBlue'),
        diffuse_color=colors.GetColor3d('LightSteelBlue'),
        specular_color=colors.GetColor3d('White'),
        specular=0.5, diffuse=0.7, ambient=0.5, specular_power=20.0,
        opacity=0.8, edge_visibility=True, line_width=1)


@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 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 FontFamily:
        VTK_ARIAL: int = 0
        VTK_COURIER: int = 1
        VTK_TIMES: int = 2
        VTK_UNKNOWN_FONT: int = 3

    @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


@dataclass(frozen=True)
class ViewPort:
    @dataclass(frozen=True)
    class Border:
        TOP: int = 0
        LEFT: int = 1
        BOTTOM: int = 2
        RIGHT: int = 3
        LEFT_BOTTOM: int = 4
        BOTTOM_RIGHT: int = 5
        RIGHT_TOP: int = 6
        RIGHT_TOP_LEFT: int = 7
        TOP_LEFT: int = 8
        TOP_LEFT_BOTTOM: int = 9
        TOP_LEFT_BOTTOM_RIGHT: int = 10
        TOP_BOTTOM: int = 11
        LEFT_RIGHT: int = 12
        NUMBER_OF_BORDER_TYPES: int = 13


if __name__ == '__main__':
    main()