Skip to content

FireFlowDemo

Repository source: FireFlowDemo

Description

This example adds interaction to FireFlow.

The example uses vtkSliderWidget's to manipulate the center of the vtkPointSource that provides seed for the streamlines. As the X, Y, or Z center corrdiantes change, the streamlines are changed. The range of the x, y, and z coordinates are limited to the bounds of the solution dataset. There is a built in delay of 500 milliseconds to make the animation between selections consistent.

Here's the embedded video


showing the interactive movement of the seeding sphere.

Cite

The solution and geometry data is from the Mayavi project. Mayavi is a python application that provides an easy to use interface to many vtk filters. Both a command-line and GUI interface are provided. If you use the Mayavi data or the Mayavi application, please use the following citation in any published work: Ramachandran, P. and Varoquaux, G., Mayavi: 3D Visualization of Scientific Data IEEE Computing in Science & Engineering, 13 (2), pp. 40-51 (2011).

Other languages

See (Cxx)

Question

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

Code

FireFlowDemo.py

#!/usr/bin/env python3

import time
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 vtkCommand
from vtkmodules.vtkFiltersCore import (
    vtkContourFilter,
    vtkTubeFilter
)
from vtkmodules.vtkFiltersFlowPaths import vtkStreamTracer
from vtkmodules.vtkFiltersGeneric import vtkGenericOutlineFilter
from vtkmodules.vtkFiltersSources import (
    vtkPointSource,
    vtkSphereSource)
from vtkmodules.vtkIOImport import vtkVRMLImporter
from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridReader
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkInteractionWidgets import (
    vtkSliderRepresentation2D,
    vtkSliderWidget)
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor
)


def get_program_parameters():
    import argparse
    description = 'Fire Flow Demonstration.'
    epilogue = '''
    The example illustrates how to combine a geometric description of a scene with a fluid flow solution.
    '''
    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('geometry',
                        help='The path to the geometry wrl file, e.g. room_vis.wrl.')
    parser.add_argument('velocity',
                        help='The path to the velocity vtu file, e.g. fire_ug.vtu.')
    parser.add_argument('-d', '--delay', type=int, default=100,
                        help='The delay in milliseconds, default=500ms.')
    args = parser.parse_args()
    return args.geometry, args.velocity, args.delay


def main():
    geometry_fn, velocity_fn, delay = get_program_parameters()
    geometry_path = Path(geometry_fn)
    velocity_path = Path(velocity_fn)
    file_check = True
    if not geometry_path.is_file():
        print(f'Missing geometry file: {geometry_path}.')
        file_check = False
    else:
        if geometry_path.suffix.lower() != '.wrl':
            print(f'The geometry file : {geometry_path} must have a .wrl suffix.')
            file_check = False

    if not Path(velocity_path).is_file():
        print(f'Missing velocity file: {velocity_path}.')
        file_check = False
    else:
        if velocity_path.suffix.lower() != '.vtu':
            print(f'The velocity file : {velocity_path} must have a .vtu suffix.')
            file_check = False
    if not file_check:
        return

    colors = vtkNamedColors()
    iso_surface_color = colors.GetColor3d('WhiteSmoke')
    sphere_color = colors.GetColor3d('HotPink')
    background_color = colors.GetColor3d('SlateGray')

    renderer = vtkRenderer(use_hidden_line_removal=True, background=background_color)

    render_window = vtkRenderWindow(size=(640, 512), window_name='FireFlowDemo')
    render_window.AddRenderer(renderer)

    render_window_interactor = vtkRenderWindowInteractor()
    render_window_interactor.render_window = render_window

    style = vtkInteractorStyleTrackballCamera()
    render_window_interactor.interactor_style = style

    # Import the VRML Files that define the geometry.
    vrml_import = vtkVRMLImporter(file_name=geometry_path, render_window=render_window)
    vrml_import.Update()

    # Read the UnstructuredGrid define the solution.
    solution = vtkXMLUnstructuredGridReader(file_name=velocity_path)
    solution.update()

    bounds = solution.output.bounds
    # center = list()
    # for i in range(0, 6, 2):
    #     print(i)
    #     center.append((bounds[i+1] - bounds[i])/2.0)
    center = (3.0, 1.6, 1.25)
    scalar_range = solution.output.scalar_range

    # Create an outline.
    outline = vtkGenericOutlineFilter()
    solution >> outline

    # Create Seeds.
    # seeds = vtkPointSource(radius=0.2, center=center, number_of_points=50)
    seeds = vtkPointSource(radius=0.2, center=center, number_of_points=50)

    # Create streamlines.
    stream_tracer = vtkStreamTracer(source_connection=seeds.output_port,
                                    maximum_propagation=50, initial_integration_step=0.2, minimum_integration_step=0.01,
                                    integrator_type=2, compute_vorticity=True)
    solution >> stream_tracer
    stream_tracer.SetIntegrationDirectionToBoth()

    tubes = vtkTubeFilter(number_of_sides=8, radius=0.02, vary_radius=False)
    stream_tracer >> tubes

    map_tubes = vtkPolyDataMapper(scalar_range=scalar_range)
    stream_tracer >> tubes >> map_tubes

    tubes_actor = vtkActor(mapper=map_tubes)

    # Create an Isosurface.
    iso_surface = vtkContourFilter()
    iso_surface.SetValue(0, 500.0)
    solution >> iso_surface

    iso_surface_mapper = vtkPolyDataMapper(scalar_visibility=False)
    solution >> iso_surface >> iso_surface_mapper

    iso_surface_actor = vtkActor(mapper=iso_surface_mapper)
    iso_surface_actor.property.opacity = 0.5
    iso_surface_actor.property.diffuse_color = iso_surface_color

    sphere = vtkSphereSource(center=seeds.center, radius=seeds.radius,
                             theta_resolution=20, phi_resolution=11)
    sphere_mapper = vtkPolyDataMapper()
    sphere >> sphere_mapper

    sphere_actor = vtkActor(mapper=sphere_mapper)
    sphere_actor.property.opacity = 1.0
    sphere_actor.property.specular = 0.4
    sphere_actor.property.specular_power = 80
    sphere_actor.property.diffuse_color = sphere_color

    renderer.AddActor(tubes_actor)
    renderer.AddActor(sphere_actor)
    renderer.AddActor(iso_surface_actor)

    sp = SliderProperties()
    sp.title_text = 'X'
    sp.range['minimum_value'] = bounds[0]
    sp.range['maximum_value'] = bounds[1]
    sp.range['value'] = seeds.center[0]
    sp.position['point1'] = (0.1, 0.08)
    sp.position['point2'] = (0.3, 0.08)
    widget_x = make_slider_widget(sp, renderer, render_window_interactor)
    cb_x = SliderCallback(0, seeds, sphere, delay)
    widget_x.AddObserver(vtkCommand.InteractionEvent, cb_x)

    sp.title_text = 'Y'
    sp.range['minimum_value'] = bounds[2]
    sp.range['maximum_value'] = bounds[3]
    sp.range['value'] = seeds.center[1]
    sp.position['point1'] = (0.4, 0.08)
    sp.position['point2'] = (0.6, 0.08)
    widget_y = make_slider_widget(sp, renderer, render_window_interactor)
    cb_y = SliderCallback(1, seeds, sphere, delay)
    widget_y.AddObserver(vtkCommand.InteractionEvent, cb_y)

    sp.title_text = 'Z'
    sp.range['minimum_value'] = bounds[4]
    sp.range['maximum_value'] = bounds[5]
    sp.range['value'] = seeds.center[2]
    sp.position['point1'] = (0.7, 0.08)
    sp.position['point2'] = (0.9, 0.08)
    widget_z = make_slider_widget(sp, renderer, render_window_interactor)
    cb_z = SliderCallback(2, seeds, sphere, delay)
    widget_z.AddObserver(vtkCommand.InteractionEvent, cb_z)

    render_window.Render()

    renderer.active_camera.Azimuth(20.0)
    renderer.active_camera.Elevation(10.0)
    renderer.active_camera.Dolly(1.25)
    renderer.ResetCameraClippingRange()

    render_window_interactor.Start()


class SliderProperties:
    dimensions = {
        'tube_width': 0.01,
        'slider_length': 0.02, 'slider_width': 0.05,
        'end_cap_length': 0.015, 'end_cap_width': 0.05,
        'title_height': 0.03, 'label_height': 0.025,
    }
    colors = {
        'title_color': 'AliceBlue', 'label_color': 'AliceBlue', 'slider_color': 'BurlyWood',
        'selected_color': 'Lime', 'bar_color': 'Black', 'bar_ends_color': 'Indigo',
        'value_color': 'DarkSlateGray'
    }
    range = {'minimum_value': 0.0, 'maximum_value': 1.0, 'value': 1.0}
    title_text = '',
    position = {'point1': (0.1, 0.05), 'point2': (0.2, 0.05)}


def make_slider_widget(slider_properties, renderer, interactor):
    """
    Make a slider widget.
    :param slider_properties: range, title name, dimensions, colors, and position.
    :param renderer: The renderer.
    :param interactor: The interactor.
    :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']

    slider_rep.renderer = renderer

    widget = vtkSliderWidget(representation=slider_rep, interactor=interactor, enabled=True)
    widget.SetNumberOfAnimationSteps(10)
    widget.SetAnimationModeToAnimate()

    return widget


class SliderCallback:
    def __init__(self, axis, seeds, sphere, delay=500):
        """
        The delay is used to make the animation between selections consistent.

        :paran: axis: The axis [0..3) corresponding to X, Y, Z.
        :param seeds: The vtkPointSource seeds.
        :param sphere: The vtkSphereSource.
        :param delay: Delay in milliseconds.
        """
        self.axis = axis
        self.seeds = seeds
        self.sphere = sphere
        self.delay = delay

    def __call__(self, caller, ev):
        slider_widget = caller
        value = slider_widget.representation.value
        center = list(self.seeds.center)
        center[self.axis] = value
        self.seeds.center = center
        self.sphere.center = center
        # Sleep for delay miliseconds.
        time.sleep(self.delay / 1000)


@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


if __name__ == '__main__':
    main()