diff --git a/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.md b/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.md index 2ad20d3636821ff6da1cc815065145181ee311a9..32dc37d671ea45a82196793a21755b1564bbb005 100644 --- a/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.md +++ b/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.md @@ -6,6 +6,9 @@ in [finite element analysis](https://en.wikipedia.org/wiki/Finite_element_method from the use of the same shape functions (or interpolation functions) to define the element's geometric shape as are used to define the displacements within the element. +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. + This example illustrates each cell's representation using its parametric coordinates (pcoords) as the vertices of the cell. In practice, the vertices will correspond to physical points in a finite element model. Use vtkTessellatorFilter to better see the shape of the cell. See for example, [QuadraticHexahedronDemo](../QuadraticHexahedronDemo) diff --git a/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.py b/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.py index 2ae798ac69a477582f885aaba3bfbbdb06cee9e6..428de8fb3ba7ac2a9ce4b73d335e634fd0632afa 100755 --- a/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.py +++ b/src/PythonicAPI/GeometricObjects/IsoparametricCellsDemo.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from collections import namedtuple from dataclasses import dataclass # noinspection PyUnresolvedReferences @@ -7,12 +8,16 @@ import vtkmodules.vtkInteractionStyle # noinspection PyUnresolvedReferences import vtkmodules.vtkRenderingOpenGL2 from vtkmodules.vtkCommonColor import vtkNamedColors +from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import ( + vtkPolyData, + vtkCellArray, vtkBiQuadraticQuad, vtkBiQuadraticQuadraticHexahedron, vtkBiQuadraticQuadraticWedge, vtkBiQuadraticTriangle, vtkCubicLine, + vtkPolyLine, vtkQuadraticEdge, vtkQuadraticHexahedron, vtkQuadraticLinearQuad, @@ -26,7 +31,15 @@ from vtkmodules.vtkCommonDataModel import ( vtkTriQuadraticHexahedron, vtkUnstructuredGrid ) -from vtkmodules.vtkFiltersSources import vtkSphereSource +# 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 ( vtkTextRepresentation, vtkTextWidget @@ -34,8 +47,12 @@ from vtkmodules.vtkInteractionWidgets import ( from vtkmodules.vtkRenderingCore import ( vtkActor, vtkActor2D, + vtkCoordinate, vtkDataSetMapper, vtkGlyph3DMapper, + vtkPolyDataMapper, + vtkPolyDataMapper2D, + vtkProperty, vtkRenderWindow, vtkRenderWindowInteractor, vtkRenderer, @@ -45,154 +62,291 @@ from vtkmodules.vtkRenderingCore import ( from vtkmodules.vtkRenderingLabel import vtkLabeledDataMapper +def get_program_parameters(): + import argparse + description = 'Demonstrate the isoparametric 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 name of the surface e.g. "Figure-8 Klein".') + 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(): - colors = vtkNamedColors() + wireframe_on, backface_on, object_num, plinth_off = get_program_parameters() - # Set up the viewports. - x_grid_dimensions = 4 - y_grid_dimensions = 4 - renderer_size = 240 - size = (renderer_size * x_grid_dimensions, renderer_size * y_grid_dimensions) - viewports = list() - for row in range(0, y_grid_dimensions): - for col in range(0, x_grid_dimensions): - # (xmin, ymin, xmax, ymax) - viewports.append([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]) - - ren_win = vtkRenderWindow(size=size, window_name='IsoparametricCellsDemo') + objects, object_order = specify_objects() - iren = vtkRenderWindowInteractor() - iren.render_window = ren_win + # 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 - u_grids, titles = get_unstructured_grids() + colors = vtkNamedColors() # Create one sphere for all. - sphere = vtkSphereSource(phi_resolution=21, theta_resolution=21, radius=0.08) + sphere = vtkSphereSource(phi_resolution=21, theta_resolution=21, radius=0.04) + + cells = get_unstructured_grids() + if single_object: + keys = [f'{objects[object_num]} (={str(object_num)})'] + else: + keys = list(cells.keys()) + + add_plinth = ('VTK_QUADRATIC_TETRA (=24)', + 'VTK_QUADRATIC_HEXAHEDRON (=25)', + 'VTK_HEXAHEDRON (=12)', + 'VTK_QUADRATIC_WEDGE (=26)', + 'VTK_QUADRATIC_PYRAMID (=27)', + 'VTK_TRIQUADRATIC_HEXAHEDRON (=29)', + 'VTK_QUADRATIC_LINEAR_WEDGE (=31)', + 'VTK_BIQUADRATIC_QUADRATIC_WEDGE (=32)', + 'VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON (=33)', + ) + lines = ('VTK_QUADRATIC_EDGE (=21)', 'VTK_CUBIC_LINE (=35)') + + # Set up the viewports. + if single_object: + grid_column_dimensions = 1 + grid_row_dimensions = 1 + renderer_size = 1200 + else: + grid_column_dimensions = 4 + grid_row_dimensions = 4 + renderer_size = 300 + + 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) # Create one text property for all. - text_property = vtkTextProperty(color=colors.GetColor3d('LightGoldenrodYellow'), bold=True, italic=True, - shadow=True, font_family_as_string='Courier', - font_size=16, justification=TextProperty.Justification.VTK_TEXT_CENTERED) + text_property = vtkTextProperty(color=colors.GetColor3d('Black'), + bold=True, italic=False, shadow=False, + font_family_as_string='Courier', + font_size=int(renderer_size / 18), + justification=TextProperty.Justification.VTK_TEXT_CENTERED) + + label_property = vtkTextProperty(color=colors.GetColor3d('DeepPink'), + bold=True, italic=False, shadow=True, + font_family_as_string='Courier', + font_size=int(renderer_size / 12), + justification=TextProperty.Justification.VTK_TEXT_CENTERED) + + back_property = vtkProperty(color=colors.GetColor3d('DodgerBlue')) # Position text according to its length and centered in the viewport. - text_positions = get_text_positions(titles, justification=TextProperty.Justification.VTK_TEXT_CENTERED) + text_positions = get_text_positions(keys, + 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('LinearCellDemo') + iren = vtkRenderWindowInteractor() + iren.render_window = ren_win + iren.interactor_style.SetCurrentStyleToTrackballCamera() + + renderers = dict() 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(u_grids)): - name = titles[i] - print('Creating:', titles[i]) - renderer = vtkRenderer(background=colors.GetColor3d('SlateGray'), viewport=viewports[i]) - - # Create the text actor and representation. - text_actors.append( - vtkTextActor(input=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].GetPositionCoordinate().value = text_positions[name]['p'] - text_representations[i].GetPosition2Coordinate().value = text_positions[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)) + # Create and link the mappers, actors and renderers together. + for key in keys: + print('Creating:', key) mapper = vtkDataSetMapper() - u_grids[i] >> mapper - + cells[key][0] >> mapper actor = vtkActor(mapper=mapper) - actor.property.color = colors.GetColor3d('Tomato') - actor.property.edge_visibility = True - actor.property.line_width = 3 - actor.property.opacity = 0.5 + 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: + actor.property.EdgeVisibilityOn() + actor.property.line_width = 3 + actor.property.color = colors.GetColor3d('Snow') + if backface_on: + actor.property.opacity = 0.4 + actor.SetBackfaceProperty(back_property) + back_property.opacity = 0.6 + else: + actor.property.opacity = 0.8 # Label the points. label_mapper = vtkLabeledDataMapper() - label_mapper.SetInputData(u_grids[i]) - label_actor = vtkActor2D() - label_actor.SetMapper(label_mapper) - - # Glyph the points - point_mapper = vtkGlyph3DMapper(input_data=u_grids[i], source_data=sphere.update().output, scaling=False, - scalar_visibility=False) - + cells[key][0] >> label_mapper + label_mapper.label_text_property = label_property + 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) - point_actor.property.diffuse_color = colors.GetColor3d('Banana') - point_actor.property.specular = 0.6 - point_actor.property.specular_color = (1.0, 1.0, 1.0) - point_actor.property.specular_power = 100 + point_actor.property.color = colors.GetColor3d('Gold') + + 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) + + 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.05) + tile_actor.GetProperty().SetColor(colors.GetColor3d('Lavender')) + tile_actor.GetProperty().SetOpacity(0.3) + renderer.AddActor(tile_actor) - renderer.AddViewProp(actor) - renderer.AddViewProp(label_actor) - renderer.AddViewProp(point_actor) + # Create the text actor and representation. + text_actor = vtkTextActor(input=key, + text_scale_mode=vtkTextActor.TEXT_SCALE_MODE_NONE, + text_property=text_property) - renderers.append(renderer) + # Create the text representation. Used for positioning the text actor. + text_representation = vtkTextRepresentation(enforce_normalized_viewport_bounds=True) + text_representation.GetPositionCoordinate().value = text_positions[key]['p'] + text_representation.GetPosition2Coordinate().value = text_positions[key]['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) - ren_win.AddRenderer(renderers[i]) + text_actors.append(text_actor) + text_representations.append(text_representation) + text_widgets.append(text_widget) - for row in range(0, y_grid_dimensions): - for col in range(0, x_grid_dimensions): - index = row * x_grid_dimensions + col + 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() - if index > (len(renderers) - 1): - # Add a renderer even if there is no actor. - # This makes the render window background all the same color. - ren = vtkRenderer(background=colors.GetColor3d('SlateGray'), viewport=viewports[index]) - ren_win.AddRenderer(ren) + renderers[key] = renderer - continue + ren_win.AddRenderer(renderers[key]) - renderers[index].ResetCamera() - renderers[index].active_camera.Azimuth(30) - renderers[index].active_camera.Elevation(-30) - renderers[index].ResetCameraClippingRange() + 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) + ren_win.AddRenderer(renderer) - for i in range(0, len(titles)): + for i in range(0, len(text_widgets)): text_widgets[i].On() - iren.Initialize() ren_win.Render() + iren.Initialize() iren.Start() +def specify_objects(): + objects = { + 21: 'VTK_QUADRATIC_EDGE', + 22: 'VTK_QUADRATIC_TRIANGLE', + 23: 'VTK_QUADRATIC_QUAD', + 36: 'VTK_QUADRATIC_POLYGON', + 24: 'VTK_QUADRATIC_TETRA', + 25: 'VTK_QUADRATIC_HEXAHEDRON', + 26: 'VTK_QUADRATIC_WEDGE', + 27: 'VTK_QUADRATIC_PYRAMID', + 28: 'VTK_BIQUADRATIC_QUAD', + 29: 'VTK_TRIQUADRATIC_HEXAHEDRON', + 30: 'VTK_QUADRATIC_LINEAR_QUAD', + 31: 'VTK_QUADRATIC_LINEAR_WEDGE', + 32: 'VTK_BIQUADRATIC_QUADRATIC_WEDGE', + 33: 'VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON', + 34: 'VTK_BIQUADRATIC_TRIANGLE', + 35: 'VTK_CUBIC_LINE', + } + object_order = list(objects.keys()) + return objects, object_order + + # These functions return a vtkUnstructured grid corresponding to the object. -def make_unstructured_grid(a_cell): - pcoords = a_cell.GetParametricCoords() - for i in range(0, a_cell.number_of_points): - a_cell.point_ids.SetId(i, i) - a_cell.points.SetPoint(i, (pcoords[3 * i]), (pcoords[3 * i + 1]), (pcoords[3 * i + 2])) +def make_ug(cell): + pcoords = cell.GetParametricCoords() + for i in range(0, cell.number_of_points): + cell.point_ids.SetId(i, i) + cell.points.SetPoint(i, (pcoords[3 * i]), (pcoords[3 * i + 1]), (pcoords[3 * i + 2])) - ug = vtkUnstructuredGrid(points=a_cell.points) - ug.InsertNextCell(a_cell.cell_type, a_cell.point_ids) + ug = vtkUnstructuredGrid(points=cell.points) + ug.InsertNextCell(cell.cell_type, cell.point_ids) return ug def make_quadratic_polygon(): - quadratic_polygon = vtkQuadraticPolygon() + number_of_vertices = 8 - quadratic_polygon.point_ids.SetNumberOfIds(8) - quadratic_polygon.point_ids.SetId(0, 0) - quadratic_polygon.point_ids.SetId(1, 1) - quadratic_polygon.point_ids.SetId(2, 2) - quadratic_polygon.point_ids.SetId(3, 3) - quadratic_polygon.point_ids.SetId(4, 4) - quadratic_polygon.point_ids.SetId(5, 5) - quadratic_polygon.point_ids.SetId(6, 6) - quadratic_polygon.point_ids.SetId(7, 7) + quadratic_polygon = vtkQuadraticPolygon() quadratic_polygon.points.SetNumberOfPoints(8) + quadratic_polygon.points.SetPoint(0, 0.0, 0.0, 0.0) quadratic_polygon.points.SetPoint(1, 2.0, 0.0, 0.0) quadratic_polygon.points.SetPoint(2, 2.0, 2.0, 0.0) @@ -203,79 +357,75 @@ def make_quadratic_polygon(): quadratic_polygon.points.SetPoint(7, 0.0, 1.0, 0.0) quadratic_polygon.points.SetPoint(5, 3.0, 1.0, 0.0) + quadratic_polygon.point_ids.SetNumberOfIds(number_of_vertices) + for i in range(0, number_of_vertices): + quadratic_polygon.point_ids.SetId(i, i) + ug = vtkUnstructuredGrid(points=quadratic_polygon.points) ug.InsertNextCell(quadratic_polygon.cell_type, quadratic_polygon.point_ids) - return ug - -def get_unstructured_grids(): - u_grids = list() - titles = list() - - u_grids.append(make_unstructured_grid( - vtkQuadraticEdge())) - titles.append('VTK_QUADRATIC_EDGE (= 21)') - - u_grids.append(make_unstructured_grid( - vtkQuadraticTriangle())) - titles.append('VTK_QUADRATIC_TRIANGLE (= 22)') - - u_grids.append(make_unstructured_grid( - vtkQuadraticQuad())) - titles.append('VTK_QUADRATIC_QUAD (= 23)') - - u_grids.append(make_quadratic_polygon()) - titles.append('VTK_QUADRATIC_POLYGON (= 36)') - - u_grids.append(make_unstructured_grid( - vtkQuadraticTetra())) - titles.append('VTK_QUADRATIC_TETRA (= 24)') - - u_grids.append(make_unstructured_grid( - vtkQuadraticHexahedron())) - titles.append('VTK_QUADRATIC_HEXAHEDRON (= 25)') - - u_grids.append(make_unstructured_grid( - vtkQuadraticWedge())) - titles.append('VTK_QUADRATIC_WEDGE (= 26)') + return ug - u_grids.append(make_unstructured_grid( - vtkQuadraticPyramid())) - titles.append('VTK_QUADRATIC_PYRAMID (= 27)') - u_grids.append(make_unstructured_grid( - vtkBiQuadraticQuad())) - titles.append('VTK_BIQUADRATIC_QUAD (= 28)') +def make_quadratic_pyramid(): + cell = vtkQuadraticPyramid() + pcoords = cell.GetParametricCoords() + for i in range(0, cell.number_of_points): + cell.point_ids.SetId(i, i) + cell.points.SetPoint(i, (pcoords[3 * i]), (pcoords[3 * i + 1]), (pcoords[3 * i + 2])) - u_grids.append(make_unstructured_grid( - vtkTriQuadraticHexahedron())) - titles.append('VTK_TRIQUADRATIC_HEXAHEDRON (= 29)') + ug = vtkUnstructuredGrid(points=cell.points) + ug.InsertNextCell(cell.cell_type, cell.point_ids) - u_grids.append(make_unstructured_grid( - vtkQuadraticLinearQuad())) - titles.append('VTK_QUADRATIC_LINEAR_QUAD (= 30)') + # pd = vtkPolyData(points=quadratic_polygon.points) + t = vtkTransform() + t.RotateX(-90) + t.Translate(0, 0, 0) + tf = vtkTransformFilter(transform=t) + (ug >> tf).update() + # pts = tf.output.GetPoints() + # for i in range(0, pts.number_of_points): + # print(f'quadratic_polygon.points.SetPoint({i}, {pts.GetPoint(i)})') + return tf.output - u_grids.append(make_unstructured_grid( - vtkQuadraticLinearWedge())) - titles.append('VTK_QUADRATIC_LINEAR_WEDGE (= 31)') - u_grids.append(make_unstructured_grid( - vtkBiQuadraticQuadraticWedge())) - titles.append('VTK_BIQUADRATIC_QUADRATIC_WEDGE (= 32)') +def get_unstructured_grids(): + """ + Get the unstructured grid names, the unstructured grid and initial orientations. - u_grids.append(make_unstructured_grid( - vtkBiQuadraticQuadraticHexahedron())) - titles.append('VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON (= 33)') + :return: A dictionary of unstructured grids. + """ - u_grids.append(make_unstructured_grid( - vtkBiQuadraticTriangle())) - titles.append('VTK_BIQUADRATIC_TRIANGLE (= 34)') + def make_orientation(azimuth: float = 0, elevation: float = 0, zoom: float = 1.0): + return Orientation(azimuth, elevation, zoom) + + return { + 'VTK_QUADRATIC_EDGE (=21)': (make_ug(vtkQuadraticEdge()), make_orientation(0, 0, 0.8)), + 'VTK_QUADRATIC_TRIANGLE (=22)': (make_ug(vtkQuadraticTriangle()), make_orientation(0, 0, 0)), + 'VTK_QUADRATIC_QUAD (=23)': (make_ug(vtkQuadraticQuad()), make_orientation(0, 0, 0)), + 'VTK_QUADRATIC_POLYGON (=36)': (make_quadratic_polygon(), make_orientation(0, 0, 0)), + 'VTK_QUADRATIC_TETRA (=24)': (make_ug(vtkQuadraticTetra()), make_orientation(20, 20, 1.0)), + 'VTK_QUADRATIC_HEXAHEDRON (=25)': (make_ug(vtkQuadraticHexahedron()), make_orientation(-22.5, 15, 0.95)), + 'VTK_QUADRATIC_WEDGE (=26)': (make_ug(vtkQuadraticWedge()), make_orientation(60, 22.5, 1.0)), + 'VTK_QUADRATIC_PYRAMID (=27)': (make_quadratic_pyramid(), make_orientation(-90 - 60, 8, 1.0)), + 'VTK_BIQUADRATIC_QUAD (=28)': (make_ug(vtkBiQuadraticQuad()), make_orientation(0, 0, 0)), + 'VTK_TRIQUADRATIC_HEXAHEDRON (=29)': (make_ug(vtkTriQuadraticHexahedron()), make_orientation(-22.5, 15, 0.95)), + 'VTK_QUADRATIC_LINEAR_QUAD (=30)': (make_ug(vtkQuadraticLinearQuad()), make_orientation(0, 0, 0)), + 'VTK_QUADRATIC_LINEAR_WEDGE (=31)': (make_ug(vtkQuadraticLinearWedge()), make_orientation(60, 22.5, 1.0)), + 'VTK_BIQUADRATIC_QUADRATIC_WEDGE (=32)': ( + make_ug(vtkBiQuadraticQuadraticWedge()), make_orientation(70, 22.5, 1.0)), + 'VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON (=33)': ( + make_ug(vtkBiQuadraticQuadraticHexahedron()), make_orientation(-22.5, 15, 0.95)), + 'VTK_BIQUADRATIC_TRIANGLE (=34)': (make_ug(vtkBiQuadraticTriangle()), make_orientation(0, 0, 0)), + 'VTK_CUBIC_LINE (=35)': (make_ug(vtkCubicLine()), make_orientation(0, 0, 0.85)), + } - u_grids.append(make_unstructured_grid( - vtkCubicLine())) - titles.append('VTK_CUBIC_LINE (= 35)') - return u_grids, titles +@dataclass(frozen=True) +class Orientation: + azimuth: float + elevation: float + zoom: float def get_text_positions(names, justification=0, vertical_justification=0, width=0.96, height=0.1): @@ -342,8 +492,167 @@ def get_text_positions(names, justification=0, vertical_justification=0, width=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.1, thickness_ratio=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. + :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] * thickness_ratio + center = ((bounds[1] + bounds[0]) / 2.0, + bounds[2] - thickness / 2.0, + (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 + tile_actor = vtkActor(mapper=plane_mapper) + + return tile_actor + + +@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 @@ -357,5 +666,25 @@ class TextProperty: 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() diff --git a/src/PythonicAPI/GeometricObjects/LinearCellDemo.md b/src/PythonicAPI/GeometricObjects/LinearCellDemo.md index b0b8d3215785d7a64be96e5d19b1665199f71f97..e2828613805d0eeceb73ebf753dc6dc0cfd0acc2 100644 --- a/src/PythonicAPI/GeometricObjects/LinearCellDemo.md +++ b/src/PythonicAPI/GeometricObjects/LinearCellDemo.md @@ -5,5 +5,6 @@ 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. diff --git a/src/PythonicAPI/GeometricObjects/LinearCellDemo.py b/src/PythonicAPI/GeometricObjects/LinearCellDemo.py index 54aa9d080fc8a97afbec188949a290b8988986ec..b37f2b9c493bdc759b9080bd4eda25371229d49e 100755 --- a/src/PythonicAPI/GeometricObjects/LinearCellDemo.py +++ b/src/PythonicAPI/GeometricObjects/LinearCellDemo.py @@ -76,14 +76,30 @@ def get_program_parameters(): 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 name of the surface e.g. "Figure-8 Klein".') parser.add_argument('-n', '--no_plinth', action='store_true', help='Remove the plinth.') args = parser.parse_args() - return args.wireframe, args.backface, args.no_plinth + return args.wireframe, args.backface, args.object_number, args.no_plinth def main(): - wireframe_on, backface_on, plinth_off = get_program_parameters() + wireframe_on, backface_on, object_num, plinth_off = get_program_parameters() + + objects, object_order = specify_objects() + + # 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() @@ -91,7 +107,10 @@ def main(): sphere = vtkSphereSource(phi_resolution=21, theta_resolution=21, radius=0.04) cells = get_unstructured_grids() - keys = list(cells.keys()) + if single_object: + keys = [f'{objects[object_num]} (={str(object_num)})'] + else: + keys = list(cells.keys()) add_plinth = ('VTK_TETRA (=10)', 'VTK_VOXEL (=11)', @@ -104,9 +123,18 @@ def main(): lines = ('VTK_LINE (=3)', 'VTK_POLY_LINE (=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 + else: + grid_column_dimensions = 4 + grid_row_dimensions = 4 + renderer_size = 300 + # Set up the viewports. + # grid_column_dimensions = 4 + # grid_row_dimensions = 4 + # renderer_size = 300 window_size = (grid_column_dimensions * renderer_size, grid_row_dimensions * renderer_size) viewports = dict() @@ -277,6 +305,29 @@ def main(): iren.Start() +def specify_objects(): + objects = { + 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', + } + object_order = list(objects.keys()) + return objects, object_order + + def get_unstructured_grids(): """ Get the unstructured grid names, the unstructured grid and initial orientations.