Skip to content


# ParametricObjectsDemo

VTKEx/Python/GeometricObjects/ParametricObjectsDemo

### Description

Demonstrates the Parametric classes added by Andrew Maclean and additional classes added by Tim Meehan. The parametric spline is also included.

Options are provided to:

- Specify a single surface (-s SURFACE_NAME)
- Save the image (-w)
- Color the back-face (-b)
- Add normals (-n)

For example:

    ParametricSurfaces -s RandomHills -w -b -n

Will write out a file called `RandomHills.png` and produce an image with all the options enabled.

    ParametricSurfaces -w

Will write out a file with no other options enabled called `ParametricObjectsDemo.png`.

Note

To really appreciate the complexity of some of these surfaces, select a single surface, and use the options `-b -n`. Also try specifying wireframe ( toggle "w" on the keyboard) and zooming in and out.

Tip

If you color the back face, the three-dimensional orientable surfaces will only show backface coloring inside the surface e.g `ConicSpiral` or `Torus`. For three dimensional non-orientable surfaces; backface coloring is visible because of the twisting used to generate these surfaces e.g `Boy` or `Figure8Klein`.

Cite

See: Parametric Equations for Surfaces, for more information. This paper gives a description of the first fifteen surfaces, including their parametric equations and derivatives. Also provided is an example of how to create your own surface, namely the Figure-8 Torus.

Other languages

See (Cxx), (CSharp)

Question

If you have a simple question about this example contact us at VTKExProject If your question is more complex and may require extended discussion, please use the VTK Discourse Forum

### Code

ParametricObjectsDemo.py

    #!/usr/bin/env python

    """
        Demonstrate all the parametric objects.
    """
    from __future__ import print_function

    import collections

    import vtk

    def get_program_parameters():
        import argparse
        description = 'Display the parametric surfaces.'
        epilogue = '''
       '''
        parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                         formatter_class=argparse.RawDescriptionHelpFormatter)
        parser.add_argument('-s', '--surface_name', default=None, help='The name of the surface.')
        parser.add_argument('-w', '--write_image', action='store_true', help='Write out the the image.')
        parser.add_argument('-b', '--back_face', action='store_true', help='Color the back-face.')
        parser.add_argument('-n', '--normals', action='store_true', help='Display normals.')
        args = parser.parse_args()
        return args.back_face, args.normals, args.surface_name, args.write_image

    def main():
        back_face, normals, surface_name, write_out_image = get_program_parameters()

        colors = vtk.vtkNamedColors()

        if surface_name:
            rendererSize = 800
            gridColumnDimensions = 1
            gridRowDimensions = 1
        else:
            rendererSize = 200
            gridColumnDimensions = 5
            gridRowDimensions = 5

        # Create one text property for all
        textProperty = vtk.vtkTextProperty()
        textProperty.SetJustificationToCentered()
        textProperty.SetFontSize(int(rendererSize / 12))
        textProperty.SetColor(colors.GetColor3d("LavenderBlush"))

        # Create a parametric function source, renderer, mapper, and actor
        # for each object
        pfnSrcs = []
        renderers = []
        mappers = []
        actors = []
        textmappers = []
        textactors = []

        # Glyph the normals
        maskPts = []
        arrow = []
        glyph = []
        glyphMapper = []
        glyphActor = []

        backProperty = vtk.vtkProperty()
        if back_face:
            backProperty.SetColor(colors.GetColor3d("Peru"))

        boundingBox = []

        # Get the parametric functions and build the pipeline
        pfn = get_parametric_functions()

        if surface_name:
            # is the surface in the map?
            surface_exists = list()
            for t in pfn.keys():
                for obj in pfn[t].keys():
                    if surface_name == obj:
                        surface_exists.append(True)
                    else:
                        surface_exists.append(False)
            if not any(surface_exists):
                print('Nonexistent object:', surface_name)
                return

        single_surface = list()
        obj_count = 0
        sorted_names = list()
        for t in sorted(pfn.keys()):
            for obj in sorted(pfn[t].keys()):
                sorted_names.append(obj)
                if surface_name:
                    if obj == surface_name:
                        single_surface = [surface_name, obj_count]
                pfnSrcs.append(vtk.vtkParametricFunctionSource())
                pfnSrcs[obj_count].SetParametricFunction(pfn[t][obj])
                pfnSrcs[obj_count].SetUResolution(51)
                pfnSrcs[obj_count].SetVResolution(51)
                pfnSrcs[obj_count].SetWResolution(51)
                pfnSrcs[obj_count].Update()

                mappers.append(vtk.vtkPolyDataMapper())
                mappers[obj_count].SetInputConnection(pfnSrcs[obj_count].GetOutputPort())

                actors.append(vtk.vtkActor())
                actors[obj_count].SetMapper(mappers[obj_count])
                actors[obj_count].GetProperty().SetColor(colors.GetColor3d("NavajoWhite"))
                if back_face:
                    actors[obj_count].SetBackfaceProperty(backProperty)

                textmappers.append(vtk.vtkTextMapper())
                textmappers[obj_count].SetInput(obj)
                textmappers[obj_count].SetTextProperty(textProperty)

                textactors.append(vtk.vtkActor2D())
                textactors[obj_count].SetMapper(textmappers[obj_count])
                textactors[obj_count].SetPosition(rendererSize / 2.0, 8)

                renderers.append(vtk.vtkRenderer())

                bounds = pfnSrcs[obj_count].GetOutput().GetBounds()
                boundingBox.append(bounds)
                # display_bounding_box_and_center(obj, obj_count, bounds)

                if normals:
                    # Glyphing
                    maskPts.append(vtk.vtkMaskPoints())
                    maskPts[obj_count].RandomModeOn()
                    maskPts[obj_count].SetMaximumNumberOfPoints(150)
                    maskPts[obj_count].SetInputConnection(pfnSrcs[obj_count].GetOutputPort())

                    arrow.append(vtk.vtkArrowSource())
                    arrow[obj_count].SetTipResolution(16)
                    arrow[obj_count].SetTipLength(0.3)
                    arrow[obj_count].SetTipRadius(0.1)

                    glyphScale = get_maximum_length(boundingBox[obj_count])

                    glyph.append(vtk.vtkGlyph3D())
                    glyph[obj_count].SetSourceConnection(arrow[obj_count].GetOutputPort())
                    glyph[obj_count].SetInputConnection(maskPts[obj_count].GetOutputPort())
                    glyph[obj_count].SetVectorModeToUseNormal()
                    glyph[obj_count].SetScaleFactor(glyphScale / 10.0)
                    glyph[obj_count].OrientOn()
                    glyph[obj_count].Update()

                    glyphMapper.append(vtk.vtkPolyDataMapper())
                    glyphMapper[obj_count].SetInputConnection(
                        glyph[obj_count].GetOutputPort())

                    glyphActor.append(vtk.vtkActor())
                    glyphActor[obj_count].SetMapper(glyphMapper[obj_count])
                    glyphActor[obj_count].GetProperty().SetColor(colors.GetColor3d("GreenYellow"))

                obj_count += 1
        # Need a renderer even if there is no actor
        for i in range(obj_count, gridColumnDimensions * gridRowDimensions):
            renderers.append(vtk.vtkRenderer())
            renderers[i].SetBackground(colors.GetColor3d("MidnightBlue"))
            sorted_names.append(None)

        renderWindow = vtk.vtkRenderWindow()
        renderWindow.SetSize(rendererSize * gridColumnDimensions, rendererSize * gridRowDimensions)

        for row in range(0, gridRowDimensions):
            for col in range(0, gridColumnDimensions):
                # (xmin, ymin, xmax, ymax)
                viewport = [
                    float(col) * rendererSize / (gridColumnDimensions * rendererSize),
                    float(gridRowDimensions - (row + 1)) * rendererSize / (gridRowDimensions * rendererSize),
                    float(col + 1) * rendererSize / (gridColumnDimensions * rendererSize),
                    float(gridRowDimensions - row) * rendererSize / (gridRowDimensions * rendererSize)]
                if not surface_name:
                    index = row * gridColumnDimensions + col
                    renderWindow.AddRenderer(renderers[index])
                    renderers[index].SetViewport(viewport)
                    if index > obj_count - 1:
                        continue
                    renderers[index].AddActor(actors[index])
                    # Normals can only be computed for polygons and triangle strips.
                    # The Spline is a line.
                    if normals and sorted_names[index] != 'Spline':
                        renderers[index].AddActor(glyphActor[index])
                    renderers[index].AddActor(textactors[index])
                    renderers[index].SetBackground(
                        colors.GetColor3d("MidnightBlue"))
                    renderers[index].ResetCamera()
                    renderers[index].GetActiveCamera().Azimuth(30)
                    renderers[index].GetActiveCamera().Elevation(-30)
                    renderers[index].GetActiveCamera().Zoom(0.9)
                    renderers[index].ResetCameraClippingRange()
                else:
                    index = single_surface[1]
                    if index != -1:
                        renderWindow.AddRenderer(renderers[index])
                        renderers[index].SetViewport(viewport)
                        renderers[index].AddActor(actors[index])
                        # Normals can only be computed for polygons and triangle strips.
                        # The Spline is a line.
                        if normals and sorted_names[index] != 'Spline':
                            renderers[index].AddActor(glyphActor[index])
                        renderers[index].AddActor(textactors[index])
                        renderers[index].SetBackground(colors.GetColor3d("MidnightBlue"))
                        renderers[index].ResetCamera()
                        renderers[index].GetActiveCamera().Azimuth(30)
                        renderers[index].GetActiveCamera().Elevation(-30)
                        renderers[index].GetActiveCamera().Zoom(0.9)
                        renderers[index].ResetCameraClippingRange()

        interactor = vtk.vtkRenderWindowInteractor()
        interactor.SetRenderWindow(renderWindow)

        renderWindow.Render()
        if surface_name:
            renderWindow.SetWindowName(single_surface[0])
        else:
            renderWindow.SetWindowName("ParametricObjectsDemo")
        renderWindow.Render()
        if write_out_image:
            # -------------------------------
            # Save the image
            # -------------------------------
            if surface_name:
                write_image(single_surface[0], renderWindow, rgba=False)
            else:
                write_image('ParametricObjectsDemo', renderWindow, rgba=False)
        interactor.Start()

    def get_parametric_functions():
        """
        Create a map of the parametric functions and set some parameters.
        The first key groups the parametric functions and the
          second key is the name of the function.

        :return: The map of functions.
        """
        # We could use OrderedDict if Python version >= 3.2
        pfn = collections.defaultdict(collections.defaultdict)
        pfn[0]['Boy'] = vtk.vtkParametricBoy()
        pfn[0]['ConicSpiral'] = vtk.vtkParametricConicSpiral()
        pfn[0]['CrossCap'] = vtk.vtkParametricCrossCap()
        pfn[0]['Dini'] = vtk.vtkParametricDini()
        pfn[0]['Ellipsoid'] = vtk.vtkParametricEllipsoid()
        pfn[0]['Enneper'] = vtk.vtkParametricEnneper()
        pfn[0]['Figure8Klein'] = vtk.vtkParametricFigure8Klein()
        pfn[0]['Klein'] = vtk.vtkParametricKlein()
        pfn[0]['Mobius'] = vtk.vtkParametricMobius()
        pfn[0]['RandomHills'] = vtk.vtkParametricRandomHills()
        pfn[0]['Roman'] = vtk.vtkParametricRoman()
        pfn[0]['SuperEllipsoid'] = vtk.vtkParametricSuperEllipsoid()
        pfn[0]['SuperToroid'] = vtk.vtkParametricSuperToroid()
        pfn[0]['Torus'] = vtk.vtkParametricTorus()
        pfn[0]['Spline'] = vtk.vtkParametricSpline()
        # Extra parametric surfaces.
        pfn[1]['BohemianDome'] = vtk.vtkParametricBohemianDome()
        pfn[1]['Bour'] = vtk.vtkParametricBour()
        pfn[1]['CatalanMinimal'] = vtk.vtkParametricCatalanMinimal()
        pfn[1]['Henneberg'] = vtk.vtkParametricHenneberg()
        pfn[1]['Kuen'] = vtk.vtkParametricKuen()
        pfn[1]['PluckerConoid'] = vtk.vtkParametricPluckerConoid()
        pfn[1]['Pseudosphere'] = vtk.vtkParametricPseudosphere()
        # Now set some parameters.
        pfn[0]["Ellipsoid"].SetXRadius(0.5)
        pfn[0]["Ellipsoid"].SetYRadius(2.0)
        pfn[0]["Mobius"].SetRadius(2.0)
        pfn[0]["Mobius"].SetMinimumV(-0.5)
        pfn[0]["Mobius"].SetMaximumV(0.5)
        pfn[0]["RandomHills"].AllowRandomGenerationOn()
        pfn[0]["RandomHills"].SetRandomSeed(1)
        pfn[0]["RandomHills"].SetNumberOfHills(30)
        pfn[0]["SuperEllipsoid"].SetN1(0.5)
        pfn[0]["SuperEllipsoid"].SetN2(0.4)
        pfn[0]["SuperToroid"].SetN1(0.5)
        pfn[0]["SuperToroid"].SetN2(3.0)
        # The spline needs points
        inputPoints = vtk.vtkPoints()
        rng = vtk.vtkMinimalStandardRandomSequence()
        rng.SetSeed(8775070)
        for p in range(0, 10):
            xyz = [None] * 3
            for idx in range(0, len(xyz)):
                xyz[idx] = rng.GetRangeValue(-1.0, 1.0)
                rng.Next()
            inputPoints.InsertNextPoint(xyz)

        pfn[0]["Spline"].SetPoints(inputPoints)
        # Extra parametric surfaces.
        pfn[1]["BohemianDome"].SetA(5.0)
        pfn[1]["BohemianDome"].SetB(1.0)
        pfn[1]["BohemianDome"].SetC(2.0)
        pfn[1]["Kuen"].SetDeltaV0(0.001)

        return pfn

    def get_centre(bounds):
        """
        Get the centre of the object from the bounding box.
        """
        if len(bounds) != 6:
            return [None] * 6
        return [bounds[i] - (bounds[i] - bounds[i - 1]) / 2.0 for i in range(1, len(bounds), 2)]

    def get_maximum_length(bounds):
        """
        Calculate the maximum length of the side bounding box.
        """
        if len(bounds) != 6:
            return None
        return max([bounds[i] - bounds[i - 1] for i in range(1, len(bounds), 2)])

    def display_bounding_box_and_center(name, index, bounds):
        if len(bounds) != 6:
            return
        max_len = get_maximum_length(bounds)
        centre = get_centre(bounds)
        s = '{:21s}: {:>2d}\n'.format(name, index)
        s += '{:21s}{:1s}'.format('  Bounds (min, max)', ':')
        s += '{:s}({:6.2f}, {:6.2f})'.format(' x:', bounds[0], bounds[1])
        s += '{:s}({:6.2f}, {:6.2f})'.format(' y:', bounds[2], bounds[3])
        s += '{:s}({:6.2f}, {:6.2f})\n'.format(' z:', bounds[4], bounds[5])
        s += '  Maximum side length: {:6.2f}\n'.format(max_len)
        s += '  Centre (x, y, z)   : ({:6.2f}, {:6.2f}, {:6.2f})\n'.format(centre[0], centre[1], centre[2])
        print(s)

    def write_image(fileName, renWin, rgba=True):
        """
        Write the render window view to an image file.

        Image types supported are:
         BMP, JPEG, PNM, PNG, PostScript, TIFF.
        The default parameters are used for all writers, change as needed.

        :param fileName: The file name, if no extension then PNG is assumed.
        :param renWin: The render window.
        :param rgba: Used to set the buffer type.
        :return:
        """

        import os

        if fileName:
            # Select the writer to use.
            path, ext = os.path.splitext(fileName)
            ext = ext.lower()
            if not ext:
                ext = '.png'
                fileName = fileName + ext
            if ext == '.bmp':
                writer = vtk.vtkBMPWriter()
            elif ext == '.jpg':
                writer = vtk.vtkJPEGWriter()
            elif ext == '.pnm':
                writer = vtk.vtkPNMWriter()
            elif ext == '.ps':
                if rgba:
                    rgba = False
                writer = vtk.vtkPostScriptWriter()
            elif ext == '.tiff':
                writer = vtk.vtkTIFFWriter()
            else:
                writer = vtk.vtkPNGWriter()

            windowto_image_filter = vtk.vtkWindowToImageFilter()
            windowto_image_filter.SetInput(renWin)
            windowto_image_filter.SetScale(1)  # image quality
            if rgba:
                windowto_image_filter.SetInputBufferTypeToRGBA()
            else:
                windowto_image_filter.SetInputBufferTypeToRGB()
                # Read from the front buffer.
                windowto_image_filter.ReadFrontBufferOff()
                windowto_image_filter.Update()

            writer.SetFileName(fileName)
            writer.SetInputConnection(windowto_image_filter.GetOutputPort())
            writer.Write()
        else:
            raise RuntimeError('Need a filename.')

    if __name__ == '__main__':
        main()