def quality(grid): """Compute the minimum scaled Jacobian cell quality of an UnstructuredGrid. Negative values indicate invalid cells while positive values indicate valid cells. Varies between -1 and 1. Examples -------- >>> import pyansys >>> import pyvista as pv >>> x = np.arange(-10, 10, 5) >>> y = np.arange(-10, 10, 5) >>> z = np.arange(-10, 10, 5) >>> x, y, z = np.meshgrid(x, y, z) >>> grid = pv.StructuredGrid(x, y, z) >>> pyansys.quality(grid) array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]) """ flip = False if isinstance(grid, pv.StructuredGrid): grid = grid.cast_to_unstructured_grid() flip = True elif not isinstance(grid, pv.UnstructuredGrid): grid = pv.wrap(grid) if not isinstance(pv.UnstructuredGrid): raise TypeError( 'Input grid should be a pyvista or vtk UnstructuredGrid') celltypes = grid.celltypes points = grid.points cells, offset = vtk_cell_info(grid) if points.dtype == np.float64: qual = cell_quality(cells, offset, celltypes, points) elif points.dtype == np.float32: qual = cell_quality_float(cells, offset, celltypes, points) else: points = points.astype(np.float64) qual = cell_quality(cells, offset, celltypes, points) # set qual of null cells to 1 qual[grid.celltypes == 0] = 1 if flip: # for strucutred grids return -qual return qual
def quality(self): """Minimum scaled jacobian cell quality. Negative values indicate invalid cells while positive values indicate valid cells. Varies between -1 and 1. Examples -------- >>> import pyansys >>> archive = pyansys.Archive(pyansys.examples.hexarchivefile, >>> archive.quality array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]) """ celltypes = self.grid.celltypes points = self.grid.points cells, offset = vtk_cell_info(self.grid) if points.dtype == np.float64: return cell_quality(cells, offset, celltypes, points) return cell_quality_float(cells, offset, celltypes, points)
def save_as_archive(filename, grid, mtype_start=1, etype_start=1, real_constant_start=1, mode='w', nblock=True, enum_start=1, nnum_start=1, include_etype_header=True, reset_etype=False, allow_missing=True, include_surface_elements=True, include_solid_elements=True): """Writes FEM as an ANSYS APDL archive file. This function supports the following element types: - ``vtk.VTK_TETRA`` - ``vtk.VTK_QUADRATIC_TETRA`` - ``vtk.VTK_PYRAMID`` - ``vtk.VTK_QUADRATIC_PYRAMID`` - ``vtk.VTK_WEDGE`` - ``vtk.VTK_QUADRATIC_WEDGE`` - ``vtk.VTK_HEXAHEDRON`` - ``vtk.VTK_QUADRATIC_HEXAHEDRON`` Will automatically renumber nodes and elements if the FEM does not contain ANSYS node or element numbers. Node numbers are stored as a point array "ansys_node_num", and cell numbers are stored as cell array "ansys_elem_num". Parameters ---------- filename : str Filename to write archive file. grid : vtk.UnstructuredGrid VTK UnstructuredGrid to convert to an APDL archive file. PolyData will automatically be converted to an unstructured mesh. mtype_start : int, optional Material number to assign to elements. Can be set manually by adding the cell array "mtype" to the unstructured grid. etype_start : int, optional Starting element type number. Can be manually set by adding the cell array "ansys_etype" to the unstructured grid. real_constant_start : int, optional Starting real constant to assign to unset cells. Can be manually set by adding the cell array "ansys_real_constant" to the unstructured grid. mode : str, optional File mode. See help(open) nblock : bool, optional Write node block when writing archive file. enum_start : int, optional Starting element number to assign to unset cells. Can be manually set by adding the cell array "ansys_elem_num" to the unstructured grid. nnum_start : int, optional Starting element number to assign to unset points. Can be manually set by adding the point array "ansys_node_num" to the unstructured grid. include_etype_header : bool, optional For each element type, includes element type command (e.g. "ET, 1, 186") in the archive file. reset_etype : bool, optional Resets element type. Element types will automatically be determined by the shape of the element (i.e. quadradic tetrahedrals will be saved as SOLID187, linear hexahedrals as SOLID185). Default True. include_surface_elements : bool, optional Includes surface elements when writing the archive file and saves them as SHELL181. include_solid_elements : bool, optional Includes solid elements when writing the archive file and saves them as SOLID185, SOLID186, or SOLID187. """ header = '/PREP7\n' if isinstance(grid, pv.PolyData): grid = grid.cast_to_unstructured_grid() allowable = [] if include_solid_elements: allowable.extend([ VTK_TETRA, VTK_QUADRATIC_TETRA, VTK_PYRAMID, VTK_QUADRATIC_PYRAMID, VTK_WEDGE, VTK_QUADRATIC_WEDGE, VTK_HEXAHEDRON, VTK_QUADRATIC_HEXAHEDRON ]) if include_surface_elements: allowable.extend([VTK_TRIANGLE, VTK_QUAD]) # VTK_QUADRATIC_TRIANGLE, # VTK_QUADRATIC_QUAD # extract allowable cell types mask = np.in1d(grid.celltypes, allowable) grid = grid.extract_cells(mask) # node numbers if 'ansys_node_num' in grid.point_arrays: nodenum = grid.point_arrays['ansys_node_num'] else: log.info('No ANSYS node numbers set in input. ' + 'Adding default range') nodenum = np.arange(1, grid.number_of_points + 1) if np.any(nodenum == -1): if not allow_missing: raise Exception( 'Missing node numbers. Exiting due "allow_missing=False"') start_num = nodenum.max() + 1 if nnum_start > start_num: start_num = nnum_start nadd = np.sum(nodenum == -1) end_num = start_num + nadd log.info('FEM missing some node numbers. Adding node numbering ' + 'from %d to %d' % (start_num, end_num)) nodenum[nodenum == -1] = np.arange(start_num, end_num) # element block ncells = grid.number_of_cells if 'ansys_elem_num' in grid.cell_arrays: enum = grid.cell_arrays['ansys_elem_num'] else: if not allow_missing: raise Exception( 'Missing node numbers. Exiting due "allow_missing=False"') log.info('No ANSYS element numbers set in input. ' + 'Adding default range starting from %d' % enum_start) enum = np.arange(1, ncells + 1) if np.any(enum == -1): if not allow_missing: raise Exception('-1 encountered in "ansys_elem_num".\n' + 'Exiting due "allow_missing=False"') start_num = enum.max() + 1 if enum_start > start_num: start_num = enum_start nadd = np.sum(enum == -1) end_num = start_num + nadd log.info('FEM missing some cell numbers. Adding numbering ' + 'from %d to %d' % (start_num, end_num)) enum[enum == -1] = np.arange(start_num, end_num) # material type if 'ansys_material_type' in grid.cell_arrays: mtype = grid.cell_arrays['ansys_material_type'] else: log.info('No ANSYS element numbers set in input. ' + 'Adding default range starting from %d' % mtype_start) mtype = np.arange(1, ncells + 1) if np.any(mtype == -1): log.info('FEM missing some material type numbers. Adding...') mtype[mtype == -1] = mtype_start # real constant if 'ansys_real_constant' in grid.cell_arrays: rcon = grid.cell_arrays['ansys_real_constant'] else: log.info('No ANSYS element numbers set in input. ' + 'Adding default range starting from %d' % real_constant_start) rcon = np.arange(1, ncells + 1) if np.any(rcon == -1): log.info('FEM missing some material type numbers. Adding...') rcon[rcon == -1] = real_constant_start # element type invalid = False if 'ansys_etype' in grid.cell_arrays and not reset_etype: missing = False typenum = grid.cell_arrays['ansys_elem_type_num'] etype = grid.cell_arrays['ansys_etype'] if np.any(etype == -1): log.warning('Some elements are missing element type numbers.') invalid = True if include_etype_header and not invalid: _, ind = np.unique(etype, return_index=True) for idx in ind: header += 'ET, %d, %d\n' % (etype[idx], typenum[idx]) else: missing = True # check if valid if not missing: mask = grid.celltypes < 20 if np.any(grid.cell_arrays['ansys_elem_type_num'][mask] == 186): invalid = True log.warning('Invalid ANSYS element types.') if invalid or missing: if not allow_missing: raise Exception( 'Invalid or missing data in "ansys_elem_type_num"' + ' or "ansys_etype". Exiting due "allow_missing=False"') log.info('No ANSYS element type or invalid data input. ' + 'Adding default range starting from %d' % etype_start) etype = np.empty(grid.number_of_cells, np.int32) etype_185 = etype_start + 2 etype[grid.celltypes == VTK_TETRA] = etype_185 etype[grid.celltypes == VTK_HEXAHEDRON] = etype_185 etype[grid.celltypes == VTK_WEDGE] = etype_185 etype[grid.celltypes == VTK_PYRAMID] = etype_185 etype_186 = etype_start etype[grid.celltypes == VTK_QUADRATIC_HEXAHEDRON] = etype_186 etype[grid.celltypes == VTK_QUADRATIC_WEDGE] = etype_186 etype[grid.celltypes == VTK_QUADRATIC_PYRAMID] = etype_186 etype_187 = etype_start + 1 etype[grid.celltypes == VTK_QUADRATIC_TETRA] = etype_187 # Surface elements etype_181 = etype_start + 3 etype[grid.celltypes == VTK_TRIANGLE] = etype_181 etype[grid.celltypes == VTK_QUAD] = etype_181 typenum = np.empty_like(etype) typenum[etype == etype_185] = 185 typenum[etype == etype_186] = 186 typenum[etype == etype_187] = 187 typenum[etype == etype_181] = 181 header += 'ET, %d, 185\n' % etype_185 header += 'ET, %d, 186\n' % etype_186 header += 'ET, %d, 187\n' % etype_187 header += 'ET, %d, 181\n' % etype_181 # number of nodes written per element elem_nnodes = np.empty(etype.size, np.int32) elem_nnodes[typenum == 181] = 4 elem_nnodes[typenum == 185] = 8 elem_nnodes[typenum == 186] = 20 elem_nnodes[typenum == 187] = 10 with open(str(filename), mode) as f: f.write(header) # write node block if write_nblock: write_nblock(f, nodenum, grid.points) # eblock header h = '' h += 'EBLOCK,19,SOLID,{:10d},{:10d}\n'.format(enum[-1], ncells) h += '(19i8)\n' f.write(h) celltypes = grid.celltypes cells, offset = vtk_cell_info(grid) if VTK9: offset += 1 for i in range(ncells): if VTK9: nodes = nodenum[cells[offset[i]:offset[i + 1]]] else: c = offset[i] nnode = cells[c] c += 1 nodes = nodenum[cells[c:c + nnode]] cellinfo = ( mtype[i], # Field 1: material reference number etype[i], # Field 2: element type number rcon[i], # Field 3: real constant reference number 1, # Field 4: section number 0, # Field 5: element coordinate system 0, # Field 6: Birth/death flag 0, # Field 7: 0, # Field 8: elem_nnodes[i], # Field 9: Number of nodes 0, # Field 10: Not Used enum[i]) # Field 11: Element number line = '%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d' % cellinfo if celltypes[i] == VTK_QUADRATIC_TETRA: if typenum[i] == 187: line += '%8d%8d%8d%8d%8d%8d%8d%8d\n%8d%8d\n' % tuple(nodes) else: # must be 186 writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[2], # 3, L (duplicate of K) nodes[3], # 4, M nodes[3], # 5, N (duplicate of M) nodes[3], # 6, O (duplicate of M) nodes[3], # 7, P (duplicate of M) nodes[4], # 8, Q nodes[5], # 9, R nodes[3], # 10, S (duplicate of K) nodes[6], # 11, T nodes[3], # 12, U (duplicate of M) nodes[3], # 13, V (duplicate of M) nodes[3], # 14, W (duplicate of M) nodes[3], # 15, X (duplicate of M) nodes[7], # 16, Y nodes[8], # 17, Z nodes[9], # 18, A nodes[9]) # 19, B (duplicate of A) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[:8] line += '%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[ 8:] elif celltypes[i] == VTK_TETRA: writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[2], # 3, L (duplicate of K) nodes[3], # 4, M nodes[3], # 5, N (duplicate of M) nodes[3], # 6, O (duplicate of M) nodes[3]) # 7, P (duplicate of M) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes elif celltypes[i] == VTK_WEDGE: writenodes = ( nodes[2], # 0, I nodes[1], # 1, J nodes[0], # 2, K nodes[0], # 3, L (duplicate of K) nodes[5], # 4, M nodes[4], # 5, N nodes[3], # 6, O nodes[3]) # 7, P (duplicate of O) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes elif celltypes[i] == VTK_QUADRATIC_WEDGE: writenodes = ( nodes[2], # 0, I nodes[1], # 1, J nodes[0], # 2, K nodes[0], # 3, L (duplicate of K) nodes[5], # 4, M nodes[4], # 5, N nodes[3], # 6, O nodes[3], # 7, P (duplicate of O) nodes[7], # 8, Q nodes[6], # 9, R nodes[0], # 10, S (duplicate of K) nodes[8], # 11, T nodes[10], # 12, U nodes[9], # 13, V nodes[3], # 14, W (duplicate of O) nodes[11], # 15, X nodes[14], # 16, Y nodes[13], # 17, Z nodes[12], # 18, A nodes[12]) # 19, B (duplicate of A) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[:8] line += '%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[ 8:] elif celltypes[i] == VTK_QUADRATIC_PYRAMID: writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[3], # 3, L nodes[4], # 4, M nodes[4], # 5, N (duplicate of M) nodes[4], # 6, O (duplicate of M) nodes[4], # 7, P (duplicate of M) nodes[5], # 8, Q nodes[6], # 9, R nodes[7], # 10, S nodes[8], # 11, T nodes[4], # 12, U (duplicate of M) nodes[4], # 13, V (duplicate of M) nodes[4], # 14, W (duplicate of M) nodes[4], # 15, X (duplicate of M) nodes[9], # 16, Y nodes[10], # 17, Z nodes[11], # 18, A nodes[12]) # 19, B (duplicate of A) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[:8] line += '%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[ 8:] elif celltypes[i] == VTK_PYRAMID: writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[3], # 3, L nodes[4], # 4, M nodes[4], # 5, N (duplicate of M) nodes[4], # 6, O (duplicate of M) nodes[4]) # 7, P (duplicate of M) line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % writenodes[:8] elif celltypes[i] == VTK_HEXAHEDRON: line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % tuple(nodes[:8]) elif celltypes[i] == VTK_QUADRATIC_HEXAHEDRON: line += '%8d%8d%8d%8d%8d%8d%8d%8d\n' % tuple(nodes[:8]) line += '%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d\n' % tuple( nodes[8:]) elif celltypes[i] == VTK_TRIANGLE: writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[2]) # 3, L (duplicate of K) line += '%8d%8d%8d%8d\n' % writenodes elif celltypes[i] == VTK_QUAD: writenodes = ( nodes[0], # 0, I nodes[1], # 1, J nodes[2], # 2, K nodes[3]) # 3, L line += '%8d%8d%8d%8d\n' % writenodes # else: # raise RuntimeError('Invalid write cell type %d' % celltypes[i]) f.write(line) f.write(' -1\n')
def _nodal_result(self, rnum, result_type, **kwargs): """Load generic nodal result Parameters ---------- rnum : int Result number. result_type : int EMS: misc. data ENF: nodal forces ENS: nodal stresses ENG: volume and energies EGR: nodal gradients EEL: elastic strains EPL: plastic strains ECR: creep strains ETH: thermal strains EUL: euler angles EFX: nodal fluxes ELF: local forces EMN: misc. non-sum values ECD: element current densities ENL: nodal nonlinear data EHC: calculated heat EPT: element temperatures ESF: element surface stresses EDI: diffusion strains ETB: ETABLE items(post1 only ECT: contact data EXY: integration point locations EBA: back stresses ESV: state variables MNL: material nonlinear record Returns ------- nnum : np.ndarray ANSYS node numbers result : np.ndarray Array of result data """ # check result exists if not self.available_results[result_type]: raise ValueError('Result %s is not available in this result file' % result_type) # element header rnum = self.parse_step_substep(rnum) result_type = result_type.upper() nitem = self._result_nitem(rnum, result_type) result_index = ELEMENT_INDEX_TABLE_KEYS.index(result_type) # Element types for nodal averaging from the global mesh n_points = self.grid.n_points cells, offset = vtk_cell_info(self.grid) data = np.zeros((n_points, nitem), np.float64) ncount = np.zeros(n_points, np.int32) c = 0 # global cell index counter for result in self._results: ele_ind_table, nodstr, etype, ptr_off = result._element_solution_header( rnum) # we return c here since it is copied to C, not passed by reference c = read_nodal_values_dist(result.filename, self.grid.celltypes, ele_ind_table, offset, cells, nitem, n_points, nodstr, etype, self._mesh.etype, result_index, ptr_off, ncount, data, c) if result_type == 'ENS' and nitem != 6: data = data[:, :6] if not np.any(ncount): raise ValueError( 'Result file contains no %s records for result %d' % (element_index_table_info[result_type.upper()], rnum)) # average across nodes data /= ncount.reshape(-1, 1) return self.grid.point_arrays['ansys_node_num'], data