├── README.md ├── node_ordering.py └── sample_lagrange_element.py /README.md: -------------------------------------------------------------------------------- 1 | # paraview-scripts 2 | Collection of python scripts for interfacing with ParaView and/or VTK. Written for the Chair for Electromagnetic Theory at Saarland University. 3 | 4 | 5 | ## Requirements 6 | Some up-to-date Python 3 version. Tested with Python 3.6. 7 | All scripts need the `numpy` package. Those creating VTK files also need the `vtk` package. 8 | 9 | ## File purposes 10 | * `node_ordering.py` generates a list of cartesian coordinates for the nodes of a single, right-angled Lagrange element at the origin in the right order (like the examples in this [Kitware blogpost](https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/)). Types: triangle, quadrilateral, tetrahedron, hexahedron, wedge. Order: 1 to 10. 11 | 12 | * `sample_lagrange_element.py` generates a VTK unstructured-grid XML `vtu` file containing a sample higher-order Lagrange element. Type and order must be specified in the script, see indicated lines. Uses `node_ordering.py` to get the coordinates. 13 | -------------------------------------------------------------------------------- /node_ordering.py: -------------------------------------------------------------------------------- 1 | # Written by Jens Ulrich Kreber 2 | # for the Chair for Electromagnetic Theory at Saarland University (https://www.uni-saarland.de/lehrstuhl/dyczij-edlinger.html) 3 | 4 | # pylint: disable=line-too-long, no-member, unsubscriptable-object 5 | """Node numbering functions for a single, right-angled lagrange element at origin""" 6 | 7 | 8 | import numpy as np 9 | 10 | def np_array(ordering): 11 | """Wrapper for np.array to simplify common modifications""" 12 | return np.array(ordering, dtype=np.float64) 13 | 14 | def n_verts_between(n, frm, to): 15 | """Places `n` vertices on the edge between `frm` and `to`""" 16 | if n <= 0: 17 | return np.ndarray((0, 3)) # empty 18 | edge_verts = np.stack(( 19 | np.linspace(frm[0], to[0], num=n+1, endpoint=False, axis=0), # n+1 since start is included, remove later 20 | np.linspace(frm[1], to[1], num=n+1, endpoint=False, axis=0), 21 | np.linspace(frm[2], to[2], num=n+1, endpoint=False, axis=0), 22 | ), axis=1) 23 | return edge_verts[1:] # remove start point 24 | 25 | def sort_by_axes(coords): 26 | coords = coords.round(12) # TODO required to some extent to get sorting right, better way to do this? 27 | reordering = np.lexsort((coords[:,0], coords[:,1], coords[:,2])) 28 | return coords[reordering, :] 29 | 30 | 31 | def number_triangle(corner_verts, order, skip=False): 32 | """Outputs the list of coordinates of a right-angled triangle of arbitrary order in the right ordering""" 33 | if order < 0: 34 | return np.ndarray((0, 3)) # empty 35 | if order == 0: # single point, for recursion 36 | assert np.isclose(corner_verts[0], corner_verts[1]).all() and np.isclose(corner_verts[0], corner_verts[2]).all() # all corners must be same point 37 | return np.array([corner_verts[0]]) 38 | 39 | # first: corner vertices 40 | coords = np_array(corner_verts) if not skip else np.ndarray((0, 3)) # empty if skip 41 | if order == 1: 42 | return coords 43 | # second: edges 44 | num_verts_on_edge = order - 1 45 | edges = [(0,1), (1,2), (2,0)] 46 | for frm, to in edges: 47 | coords = np.concatenate([coords, n_verts_between(num_verts_on_edge, corner_verts[frm], corner_verts[to])], axis=0) if not skip else coords # do nothing if skip 48 | if order == 2: 49 | return coords 50 | # third: face, use recursion 51 | e_x = (corner_verts[1] - corner_verts[0]) / order 52 | e_y = (corner_verts[2] - corner_verts[0]) / order 53 | inc = np.array([e_x + e_y, -2*e_x + e_y, e_x -2*e_y]) # adjust corner vertices for recursion 54 | return np.concatenate([coords, number_triangle(np.array(corner_verts) + inc, order - 3, skip=False)], axis=0) # recursive call, decrease order 55 | 56 | 57 | def number_tetrahedron(corner_verts, order): 58 | """Outputs the list of coordinates of a right-angled tetrahedron of arbitrary order in the right ordering""" 59 | if order < 0: 60 | return np.ndarray((0, 3)) # empty 61 | if order == 0: # single point 62 | assert np.isclose(corner_verts[1], corner_verts[0]).all() and np.isclose(corner_verts[2], corner_verts[0]).all() and np.isclose(corner_verts[3], corner_verts[0]).all() # all corners must be same point 63 | return np.array([corner_verts[0]]) 64 | 65 | # first: corner vertices 66 | coords = np_array(corner_verts) 67 | if order == 1: 68 | return coords 69 | # second: edges 70 | num_verts_on_edge = order - 1 71 | edges = [(0,1), (1,2), (2,0), (0,3), (1,3), (2,3)] 72 | for frm, to in edges: 73 | coords = np.concatenate([coords, n_verts_between(num_verts_on_edge, corner_verts[frm], corner_verts[to])], axis=0) 74 | if order == 2: 75 | return coords 76 | # third: faces, use triangle numbering method 77 | faces = [(0,1,3), (2,3,1), (0,3,2), (0,2,1)] # x-z, top, y-z, x-y (CCW) TODO: not as in documentation, beware of future changes!! 78 | for v_x, v_y, v_z in faces: 79 | coords = np.concatenate([coords, number_triangle([corner_verts[v_x], corner_verts[v_y], corner_verts[v_z]], order, skip=True)], axis=0) # use number_triangle to number face, but skip corners and edges 80 | if order == 3: 81 | return coords 82 | # fourth: volume, use recursion 83 | e_x = (corner_verts[1] - corner_verts[0]) / order 84 | e_y = (corner_verts[2] - corner_verts[0]) / order 85 | e_z = (corner_verts[3] - corner_verts[0]) / order 86 | inc = np.array([e_x + e_y + e_z, -3*e_x + e_y + e_z, e_x -3*e_y + e_z, e_x + e_y -3*e_z]) # adjust corner vertices for recursion 87 | return np.concatenate([coords, number_tetrahedron(np.array(corner_verts) + inc, order - 4)], axis=0) # recursive call, decrease order 88 | 89 | 90 | def number_quadrilateral(corner_verts, order, skip=False): 91 | """Outputs the list of coordinates of a right-angled quadrilateral of arbitrary order in the right ordering""" 92 | # first: corner vertices 93 | coords = np_array(corner_verts) if not skip else np.ndarray((0, 3)) # empty if skip 94 | # second: edges 95 | num_verts_on_edge = order -1 96 | edges = [(0,1), (1,2), (3,2), (0,3)] 97 | for frm, to in edges: 98 | coords = np.concatenate([coords, n_verts_between(num_verts_on_edge, corner_verts[frm], corner_verts[to])], axis=0) if not skip else np.ndarray((0, 3)) # empty if skip 99 | # third: face 100 | e_x = (corner_verts[1] - corner_verts[0]) / order 101 | e_y = (corner_verts[3] - corner_verts[0]) / order 102 | pos_y = corner_verts[0].copy() 103 | pos_y = np.expand_dims(pos_y, axis=0) 104 | for _ in range(num_verts_on_edge): 105 | pos_y += e_y 106 | pos_yx = pos_y.copy() 107 | for _ in range(num_verts_on_edge): 108 | pos_yx += e_x 109 | coords = np.concatenate([coords, pos_yx], axis=0) 110 | return coords 111 | 112 | 113 | def number_hexahedron(corner_verts, order): 114 | """Outputs the list of coordinates of a right-angled hexahedron of arbitrary order in the right ordering""" 115 | # first: corner vertices 116 | coords = np_array(corner_verts) 117 | # second: edges 118 | num_verts_on_edge = order - 1 119 | edges = [(0,1), (1,2), (3,2), (0,3), (4,5), (5,6), (7,6), (4,7), (0,4), (1,5), (3,7), (2,6)] # TODO: not as in documentation, beware of future changes!! 120 | for frm, to in edges: 121 | coords = np.concatenate([coords, n_verts_between(num_verts_on_edge, corner_verts[frm], corner_verts[to])], axis=0) 122 | # third: faces 123 | faces = [(0,3,7,4), (1,2,6,5), (0,1,5,4), (3,2,6,7), (0,1,2,3), (4,5,6,7)] # TODO: not as in documentation, beware of future changes!! 124 | for indices in faces: 125 | sub_corner_verts = [corner_verts[q] for q in indices] 126 | face_coords = number_quadrilateral(np_array(sub_corner_verts), order, skip=True) # use number_quadrilateral to number face, but skip cornes and edges 127 | coords = np.concatenate([coords, face_coords], axis=0) 128 | # fourth: interior 129 | e_x = (corner_verts[1] - corner_verts[0]) / order 130 | e_y = (corner_verts[3] - corner_verts[0]) / order 131 | e_z = (corner_verts[4] - corner_verts[0]) / order 132 | pos_z = corner_verts[0].copy() 133 | pos_z = np.expand_dims(pos_z, axis=0) 134 | for _ in range(num_verts_on_edge): 135 | pos_z += e_z 136 | pos_zy = pos_z.copy() 137 | for _ in range(num_verts_on_edge): 138 | pos_zy += e_y 139 | pos_zyx = pos_zy.copy() 140 | for _ in range(num_verts_on_edge): 141 | pos_zyx += e_x 142 | coords = np.concatenate([coords, pos_zyx], axis=0) 143 | return coords 144 | 145 | 146 | def number_wedge(corner_verts, order): # currently only works up to 4th order, either very weird node numbering for triangular faces above or a bug in vtk 147 | """Outputs the list of coordinates of a right-angled hexahedron of arbitrary order in the right ordering""" 148 | # first: corner vertices 149 | coords = np_array(corner_verts) 150 | # second: edges 151 | num_verts_on_edge = order - 1 152 | edges = [(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (0,3), (1,4), (2,5)] 153 | for frm, to in edges: 154 | coords = np.concatenate([coords, n_verts_between(num_verts_on_edge, corner_verts[frm], corner_verts[to])], axis=0) 155 | # third: faces 156 | triangular_faces = [(0,1,2), (3,4,5)] 157 | quadrilateral_faces = [(0,1,4,3), (1,2,5,4), (0,2,5,3)] 158 | for indices in triangular_faces: 159 | face_coords = number_triangle(np_array([corner_verts[q] for q in indices]), order, skip=True) # use number_triangle to number face, but skip corners and edges 160 | face_coords = sort_by_axes(face_coords) # ! face points on triangles are not reported like normal triangles, but in axis order. Only on wedges ! 161 | coords = np.concatenate([coords, face_coords], axis=0) 162 | for indices in quadrilateral_faces: 163 | coords = np.concatenate([coords, number_quadrilateral(np_array([corner_verts[q] for q in indices]), order, skip=True)], axis=0) # use number_quadrilateral to number face, but skip corners and edges 164 | # fourth: interior 165 | e_z = (corner_verts[3] - corner_verts[0]) / order 166 | pos_z = corner_verts[0].copy() 167 | pos_z = np.expand_dims(pos_z, axis=0) 168 | for _ in range(num_verts_on_edge): 169 | pos_z += e_z 170 | interior_triag_corner_verts = np_array([corner_verts[0], corner_verts[1], corner_verts[2]]) + pos_z 171 | face_coords = number_triangle(interior_triag_corner_verts, order, skip=True) # use number_triangle to number face, but skip corners and edges 172 | face_coords = sort_by_axes(face_coords) # ! face points on triangles are not reported like normal triangles, but in axis order. Only on wedges ! 173 | coords = np.concatenate([coords, face_coords], axis=0) 174 | return coords 175 | 176 | 177 | def node_ordering(element_type, order): 178 | order = int(order) 179 | if order < 1 or order > 10: 180 | raise ValueError("order must in interval [1, 10]") 181 | if element_type == 'triangle': 182 | return number_triangle(np_array([[0, 0, 0], [1, 0, 0], [0, 1, 0]]), order) 183 | if element_type == 'tetrahedron': 184 | return number_tetrahedron(np_array([[0,0,0], [1,0,0], [0,1,0], [0,0,1]]), order) 185 | if element_type == 'quadrilateral': 186 | return number_quadrilateral(np_array([[0,0,0], [1,0,0], [1,1,0], [0,1,0]]), order) 187 | if element_type == 'hexahedron': 188 | return number_hexahedron(np_array([[0,0,0], [1,0,0], [1,1,0], [0,1,0], [0,0,1], [1,0,1], [1,1,1], [0,1,1]]), order) 189 | if element_type == 'wedge': 190 | return number_wedge(np_array([[0,0,0], [1,0,0], [0,1,0], [0,0,1], [1,0,1], [0,1,1]]), order) 191 | raise ValueError("Unknown element type '" + str(element_type) + "'") 192 | -------------------------------------------------------------------------------- /sample_lagrange_element.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | 3 | # Written by Jens Ulrich Kreber 4 | # for the Chair for Electromagnetic Theory at Saarland University (https://www.uni-saarland.de/lehrstuhl/dyczij-edlinger.html) 5 | 6 | # pylint: disable=line-too-long, no-member, unsubscriptable-object 7 | """Generate a sample Lagrange element file to check on vertex ordering. Change the code on the indicated lines for desired element type and order or specify them as arguments""" 8 | 9 | 10 | import warnings 11 | import sys 12 | import numpy as np 13 | import vtk 14 | import vtk.util.numpy_support as vnp 15 | from node_ordering import node_ordering 16 | 17 | VTK_CELLTYPES = {'triangle': vtk.VTK_LAGRANGE_TRIANGLE, 'tetrahedron': vtk.VTK_LAGRANGE_TETRAHEDRON, 'quadrilateral': vtk.VTK_LAGRANGE_QUADRILATERAL, 'hexahedron': vtk.VTK_LAGRANGE_HEXAHEDRON, 'wedge': vtk.VTK_LAGRANGE_WEDGE} 18 | 19 | if __name__ == "__main__": 20 | warnings.simplefilter(action='ignore', category=FutureWarning) # TODO: check for fixed version of vtk 21 | 22 | ### change to desired element here 23 | element_type = 'wedge' # <------------------------ one of 'triangle', 'quadrilateral', 'tetrahedron', 'hexahedron', 'wedge' 24 | element_order = 5 # <------------------------ 1 to 10 25 | ### 26 | 27 | if len(sys.argv) == 3 and sys.argv[1] in VTK_CELLTYPES.keys() and int(sys.argv[2]) in range(1,11): 28 | element_type = sys.argv[1] 29 | element_order = int(sys.argv[2]) 30 | elif len(sys.argv) != 1: 31 | sys.exit("Usage: " + sys.argv[0] + " [ELEMENT_TYPE ELEMENT_ORDER]") 32 | 33 | points = node_ordering(element_type, element_order) 34 | points_vtk_data = vnp.numpy_to_vtk(points) 35 | points_vtk = vtk.vtkPoints() 36 | points_vtk.SetData(points_vtk_data) 37 | 38 | num_points = points.shape[0] 39 | cell_data = np.concatenate((np.array([num_points]), np.arange(num_points)), axis=0) 40 | id_type_np = vnp.ID_TYPE_CODE # the numpy dtype for vtkIdTypeArray 41 | cell_data_vtk = vnp.numpy_to_vtk(cell_data, array_type=vtk.VTK_ID_TYPE) 42 | cells = vtk.vtkCellArray() 43 | cells.SetCells(1, cell_data_vtk) 44 | 45 | pointdata = np.arange(num_points, dtype=np.float64) # simply use point index as data 46 | pointdata_vtk = vnp.numpy_to_vtk(pointdata) 47 | pointdata_vtk.SetName("point_numbers") 48 | 49 | ugrid = vtk.vtkUnstructuredGrid() 50 | ugrid.SetPoints(points_vtk) 51 | ugrid.SetCells(VTK_CELLTYPES[element_type], cells) 52 | pointdata_container = ugrid.GetPointData() 53 | pointdata_container.SetScalars(pointdata_vtk) 54 | 55 | writer = vtk.vtkXMLUnstructuredGridWriter() 56 | writer.SetInputDataObject(ugrid) 57 | writer.SetDataModeToAscii() 58 | writer.SetCompressorTypeToNone() 59 | writer.SetFileName('lagrange_sample.vtu') 60 | writer.Write() 61 | --------------------------------------------------------------------------------