def generate_disk_mesh(radius=1.0, theta_max=np.pi, nr=2, ntheta=4, center=(0, 0, 0), normal=(1, 0, 0), name=None) -> Mesh: theta_range = np.linspace(0, 2 * theta_max, ntheta + 1) r_range = np.linspace(0.0, radius, nr + 1) nodes = np.zeros(((ntheta + 1) * (nr + 1), 3), dtype=float) for i, (r, t) in enumerate(product(r_range, theta_range)): y = +r * np.sin(t) z = -r * np.cos(t) nodes[i, :] = (0, y, z) panels = np.zeros((ntheta * nr, 4), dtype=int) for k, (i, j) in enumerate(product(range(0, nr), range(0, ntheta))): panels[k, :] = (j + i * (ntheta + 1), j + 1 + i * (ntheta + 1), j + 1 + (i + 1) * (ntheta + 1), j + (i + 1) * (ntheta + 1)) mesh = Mesh(nodes, panels, name=name) mesh.merge_duplicates() mesh.heal_triangles() mesh.rotate_around_center_to_align_vectors( (0, 0, 0), mesh.faces_normals[0], normal) mesh.translate(center) return mesh
def _generate_open_cylinder_mesh(self, nx, ntheta, reflection_symmetry, translation_symmetry, name=None): """Open horizontal cylinder using the symmetries (translation and reflection) to speed up the computations""" theta_max = np.pi theta = np.linspace(0, theta_max, ntheta // 2 + 1) X = np.array([0, self.length / nx]) # Nodes nodes = np.zeros(((ntheta // 2 + 1) * 2, 3), dtype=float) for i, (t, x) in enumerate(product(theta, X)): y = +self.radius * np.sin(t) z = -self.radius * np.cos(t) nodes[i, :] = (x, y, z) nodes += -np.array([self.length / 2, 0, 0]) # Connectivities panels = np.zeros((ntheta // 2, 4), dtype=int) for k, i in enumerate(range(0, ntheta // 2)): panels[k, :] = (2 * i, 2 * i + 2, 2 * i + 3, 2 * i + 1) half_ring = Mesh(nodes, panels, name=f"half_ring_of_{name}_mesh") if reflection_symmetry: if nx == 1: half_cylinder = half_ring else: half_cylinder = TranslationalSymmetricMesh( half_ring, translation=np.asarray([self.length / nx, 0.0, 0.0]), nb_repetitions=nx - 1, name=f"half_{name}_mesh") if not translation_symmetry: half_cylinder = half_cylinder.merged() return ReflectionSymmetricMesh(half_cylinder, plane=xOz_Plane, name=f"{name}_mesh") else: strip = half_ring + half_ring.mirrored(plane=xOz_Plane) if nx == 1: return strip else: cylinder = TranslationalSymmetricMesh( strip, translation=np.asarray([self.length / nx, 0.0, 0.0]), nb_repetitions=nx - 1, name=f"half_{name}_mesh") if not translation_symmetry: cylinder = cylinder.merged() return cylinder
def load_MAR(filename, name=None): """Loads Nemoh (Ecole Centrale de Nantes) mesh files. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh or ReflectionSymmetry the loaded mesh Note ---- MAR files have a 1-indexing """ _check_file(filename) ifile = open(filename, 'r') header = ifile.readline() _, symmetric_mesh = header.split() vertices = [] while 1: line = ifile.readline() line = line.split() if line[0] == '0': break vertices.append(list(map(float, line[1:]))) vertices = np.array(vertices, dtype=np.float) faces = [] while 1: line = ifile.readline() line = line.split() if line[0] == '0': break faces.append(list(map(int, line))) faces = np.array(faces, dtype=np.int) ifile.close() if int(symmetric_mesh) == 1: if name is None: half_mesh = Mesh(vertices, faces - 1) return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane) else: half_mesh = Mesh(vertices, faces - 1, name=f"half_of_{name}") return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=name) else: return Mesh(vertices, faces - 1, name)
def test_mesh_initialization(): """Test how the code checks the validity of the parameters.""" with pytest.raises(AssertionError): Mesh(vertices=np.random.rand(6, 3), faces=[(0, 1, 2), (3, 4, 5)]) with pytest.raises(AssertionError): Mesh(vertices=np.random.rand(4, 3), faces=[(0, 1, 2, -1)]) with pytest.raises(AssertionError): Mesh(vertices=np.random.rand(3, 3), faces=[(0, 1, 2, 3)])
def clip(source_mesh: Mesh, plane: Plane, vicinity_tol=1e-3, name=None): """Return a new mesh containing the source mesh clipped by the plane. Parameters ---------- source_mesh : Mesh The mesh to be clipped. plane : Plane, optional The clipping plane. vicinity_tol : float, optional The absolute tolerance to consider en vertex is on the plane. Default is 1e-3. name: string, optional A name for the new clipped mesh. """ vertices_data = _vertices_positions_wrt_plane(source_mesh, plane, vicinity_tol) nb_vertices_above_or_on_plane = np.count_nonzero( vertices_data['vertices_above_mask'] | vertices_data['vertices_on_mask']) nb_vertices_below_or_on_plane = np.count_nonzero( vertices_data['vertices_below_mask'] | vertices_data['vertices_on_mask']) if nb_vertices_above_or_on_plane == source_mesh.nb_vertices: LOG.warning( f"Clipping {source_mesh.name} by {plane}: all vertices are removed." ) clipped_mesh = Mesh(None, None) clipped_mesh._clipping_data = dict(faces_ids=[]) elif nb_vertices_below_or_on_plane == source_mesh.nb_vertices: LOG.info(f"Clipping {source_mesh.name} by {plane}: no action.") clipped_mesh = source_mesh.copy() clipped_mesh._clipping_data = dict( faces_ids=list(range(source_mesh.nb_faces))) else: upper_mesh, crown_mesh, lower_mesh = _partition_mesh( vertices_data, source_mesh) if crown_mesh.nb_faces > 0: clipped_crown_mesh = _clip_crown(crown_mesh, plane) clipped_mesh = lower_mesh + clipped_crown_mesh clipped_mesh._clipping_data = { 'faces_ids': np.concatenate( (lower_mesh._clipping_data['faces_ids'], clipped_crown_mesh._clipping_data['faces_ids'])) } else: clipped_mesh = lower_mesh if name is None: clipped_mesh.name = f'{source_mesh.name}_clipped' clipped_mesh.remove_unused_vertices() return clipped_mesh
def test_mesh_naming(): """Test how the mesh handle names and string representation.""" # Test string representation assert str(test_mesh) == 'test_mesh' # Just the name assert repr( test_mesh ) == "Mesh(nb_vertices=4, nb_faces=1, name=test_mesh)" # A longer representation. # Test automatic naming dummy_mesh = Mesh() # Automatically named something like mesh_1 other_dummy_mesh = Mesh() # Automatically named something like mesh_2 assert dummy_mesh.name[:5] == "mesh_" assert other_dummy_mesh.name[:5] == "mesh_" assert int(dummy_mesh.name[5:]) + 1 == int(other_dummy_mesh.name[5:])
def from_meshio(mesh, name=None) -> 'FloatingBody': """Create a FloatingBody from a meshio mesh object.""" import meshio if not isinstance(mesh, meshio._mesh.Mesh): raise TypeError( 'mesh must be of type meshio._mesh.Mesh, recevied {:}'.format( type(mesh))) if name is None: date_str = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f') name = 'fb_{:}'.format(date_str) def all_faces_as_quads(cells): all_faces = [] if 'quad' in cells: all_faces.append(cells['quad']) if 'triangle' in cells: num_triangles = len(mesh.cells_dict['triangle']) LOG.info("Stored {:} triangle faces as quadrilaterals".format( num_triangles)) triangles_as_quads = np.empty((cells['triangle'].shape[0], 4), dtype=int) triangles_as_quads[:, :3] = cells['triangle'][:, :] triangles_as_quads[:, 3] = cells[ 'triangle'][:, 2] # Repeat one node to make a quad all_faces.append(triangles_as_quads) return np.concatenate(all_faces) cpt_mesh = Mesh(vertices=mesh.points, faces=all_faces_as_quads(mesh.cells_dict), name=name + "_mesh") fb = FloatingBody(mesh=cpt_mesh, name=name) return fb
def test_vertices_and_faces_get_and_set(): """Test get and set functions for vertices and faces.""" # Get faces faces = cylinder.faces vertices = cylinder.vertices new_cylinder = Mesh(name="new_cylinder") # Set faces with pytest.raises(AssertionError): new_cylinder.faces = faces # You should set the vertices first. new_cylinder.vertices = vertices new_cylinder.faces = faces assert new_cylinder == cylinder
def load_VTP(filename, name=None): """Loads VTK file format in the new XML format (vtp file extension for polydata meshes). It relies on the reader from the VTK library. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- VTP files have a 0-indexing """ _check_file(filename) vtk = import_optional_dependency("vtk") reader = vtk.vtkXMLPolyDataReader() reader.SetFileName(filename) reader.Update() vtk_mesh = reader.GetOutput() vertices, faces = _dump_vtk(vtk_mesh) return Mesh(vertices, faces, name)
def __init__(self, mesh=None, dofs=None, mass=None, center_of_mass=None, name=None): if mesh is None: mesh = Mesh(name="dummy_mesh") if dofs is None: dofs = {} if name is None: name = mesh.name assert isinstance(mesh, Mesh) or isinstance(mesh, CollectionOfMeshes) self.mesh = mesh self.full_body = None self.dofs = dofs self.mass = mass self.center_of_mass = center_of_mass self.name = name if self.mesh.nb_vertices == 0 or self.mesh.nb_faces == 0: LOG.warning(f"New floating body (with empty mesh!): {self.name}.") else: self.mesh.heal_mesh() LOG.info(f"New floating body: {self.name}.")
def load_MED(filename, name=None): """Loads MED mesh files generated by SALOME MECA. Parameters ---------- filename: str name of the mesh file on disk Returns ------- Mesh the loaded mesh Note ---- MED files have a 1-indexing """ try: import h5py except ImportError: raise ImportError('MED file format reader needs h5py module to be installed') _check_file(filename) file = h5py.File(filename) list_of_names = [] file.visit(list_of_names.append) # TODO: gerer les cas ou on a que des tris ou que des quads... nb_quadrangles = nb_triangles = 0 for item in list_of_names: if '/NOE/COO' in item: vertices = file.get(item).value.reshape((3, -1)).T nv = vertices.shape[0] if '/MAI/TR3/NOD' in item: triangles = file.get(item).value.reshape((3, -1)).T - 1 nb_triangles = triangles.shape[0] if '/MAI/QU4/NOD' in item: quadrangles = file.get(item).value.reshape((4, -1)).T - 1 nb_quadrangles = quadrangles.shape[0] file.close() if nb_triangles == 0: triangles = np.zeros((0, 4), dtype=int) else: triangles = np.column_stack((triangles, triangles[:, 0])) if nb_quadrangles == 0: quadrangles = np.zeros((0, 4), dtype=int) faces = np.zeros((nb_triangles+nb_quadrangles, 4), dtype=int) faces[:nb_triangles] = triangles # faces[:nb_triangles, -1] = triangles[:, 0] faces[nb_triangles:] = quadrangles vertices = np.ascontiguousarray(vertices) return Mesh(vertices, faces)
def load_VTK(filename, name=None): """Loads VTK file format in the legacy format (vtk file extension). It relies on the reader from the VTK library. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- VTU files have a 0-indexing """ _check_file(filename) from vtk import vtkPolyDataReader reader = vtkPolyDataReader() reader.SetFileName(filename) reader.Update() vtk_mesh = reader.GetOutput() vertices, faces = _dump_vtk(vtk_mesh) return Mesh(vertices, faces, name)
def load_NAT(filename, name=None): """This function loads natural file format for meshes. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Notes ----- The file format is as follow:: xsym ysym n m x1 y1 z1 . . . xn yn zn i1 j1 k1 l1 . . . im jm km lm where : n : number of nodes m : number of cells x1 y1 z1 : cartesian coordinates of node 1 i1 j1 k1 l1 : counterclock wise Ids of nodes for cell 1 if cell 1 is a triangle, i1==l1 Note ---- NAT files have a 1-indexing """ _check_file(filename) ifile = open(filename, 'r') ifile.readline() nv, nf = list(map(int, ifile.readline().split())) vertices = [] for i in range(nv): vertices.append(list(map(float, ifile.readline().split()))) vertices = np.array(vertices, dtype=np.float) faces = [] for i in range(nf): faces.append(list(map(int, ifile.readline().split()))) faces = np.array(faces, dtype=np.int) ifile.close() return Mesh(vertices, faces - 1, name)
def test_clipper_corner_cases(): mesh = sphere.translated_z(10.0) plane = Plane(point=(0, 0, 0), normal=(0, 0, 1)) clipped_mesh = mesh.clip(plane, inplace=False) assert clipped_mesh == Mesh(None, None) # Empty mesh plane = Plane(point=(0, 0, 0), normal=(0, 0, -1)) clipped_mesh = mesh.clip(plane, inplace=False) assert clipped_mesh == mesh # Unchanged mesh # Two distinct bodies two_spheres = Mesh.join_meshes(sphere.translated_z(10.0), sphere.translated_z(-10.0)) plane = Plane(point=(0, 0, 0), normal=(0, 0, -1)) one_sphere_remaining = two_spheres.clip(plane, inplace=False) assert one_sphere_remaining == sphere.translated_z(10.0)
def load_GDF(filename, name=None): """Loads WAMIT (Wamit INC. (c)) GDF mesh files. As GDF file format maintains a redundant set of vertices for each faces of the mesh, it returns a merged list of nodes and connectivity array by using the merge_duplicates function. Parameters ---------- filename: str name of the mesh file on disk Returns ------- Mesh the loaded mesh Note ---- GDF files have a 1-indexing """ _check_file(filename) ifile = open(filename, 'r') ifile.readline() # skip one header line line = ifile.readline().split() ulen = line[0] grav = line[1] line = ifile.readline().split() isx = line[0] isy = line[1] line = ifile.readline().split() nf = int(line[0]) vertices = np.zeros((4 * nf, 3), dtype=float) faces = np.zeros((nf, 4), dtype=int) iv = 0 for icell in range(nf): n_coords = 0 face_coords = np.zeros((12,), dtype=float) while n_coords < 12: line = np.array(ifile.readline().split()) face_coords[n_coords:n_coords+len(line)] = line n_coords += len(line) vertices[iv:iv+4, :] = np.split(face_coords, 4) faces[icell, :] = np.arange(iv, iv+4) iv += 4 ifile.close() return Mesh(vertices, faces, name)
def mesh_from_set_of_faces(): A, B, C, D = (0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0) # Two triangular faces faces = {frozenset({A, B, C}), frozenset({B, C, D})} mesh = Mesh.from_set_of_faces(faces) assert mesh.nb_vertices == 4 assert mesh.nb_faces == 2 assert mesh.is_triangle(0) and mesh.is_triangle(1)
def test_LinearPotentialFlowProblem(): # Without a body pb = LinearPotentialFlowProblem(omega=1.0) assert pb.omega == 1.0 assert pb.period == 2 * np.pi assert pb.wavenumber == 1.0 / 9.81 assert pb.wavelength == 9.81 * 2 * np.pi assert LinearPotentialFlowProblem(free_surface=np.infty, sea_bottom=-np.infty).depth == np.infty assert LinearPotentialFlowProblem(free_surface=0.0, sea_bottom=-np.infty).depth == np.infty pb = LinearPotentialFlowProblem(free_surface=0.0, sea_bottom=-1.0, omega=1.0) assert pb.depth == 1.0 assert np.isclose(pb.omega**2, pb.g * pb.wavenumber * np.tanh(pb.wavenumber * pb.depth)) assert pb.dimensionless_wavenumber == pb.wavenumber * 1.0 with pytest.raises(NotImplementedError): LinearPotentialFlowProblem(free_surface=2.0) with pytest.raises(NotImplementedError): LinearPotentialFlowProblem(free_surface=np.infty, sea_bottom=0.0) with pytest.raises(ValueError): LinearPotentialFlowProblem(free_surface=0.0, sea_bottom=1.0) with pytest.raises(TypeError): LinearPotentialFlowProblem(wave_direction=1.0) with pytest.raises(TypeError): LinearPotentialFlowProblem(radiating_dof="Heave") with pytest.raises(ValueError): LinearPotentialFlowProblem(body=FloatingBody(mesh=Mesh([], []))) # With a body sphere = Sphere(center=(0, 0, -2.0)) sphere.add_translation_dof(direction=(0, 0, 1), name="Heave") pb = LinearPotentialFlowProblem(body=sphere, omega=1.0) pb.boundary_condition = sphere.mesh.faces_normals @ (1, 1, 1) assert list(pb.influenced_dofs.keys()) == ['Heave'] pb2 = LinearPotentialFlowProblem(body=sphere, omega=2.0) pb2.boundary_condition = sphere.mesh.faces_normals @ (1, 1, 1) assert pb < pb2 # Test transformation to result class res = pb.make_results_container() assert isinstance(res, LinearPotentialFlowResult) assert res.problem is pb assert res.omega == pb.omega assert res.period == pb.period assert res.body is pb.body
def generate_rectangle_mesh(width=1.0, height=1.0, nw=1, nh=1, center=(0, 0, 0), normal=(1, 0, 0), name=None): Y = np.linspace(-width / 2, width / 2, nw + 1) Z = np.linspace(-height / 2, height / 2, nh + 1) nodes = np.zeros(((nw + 1) * (nh + 1), 3), dtype=np.float) panels = np.zeros((nw * nh, 4), dtype=np.int) for i, (x, y, z) in enumerate(product([0.0], Y, Z)): nodes[i, :] = x, y, z for k, (i, j) in enumerate(product(range(0, nw), range(0, nh))): panels[k, :] = (j + i * (nh + 1), j + 1 + i * (nh + 1), j + 1 + (i + 1) * (nh + 1), j + (i + 1) * (nh + 1)) if name is None: name = f"rectangle_{next(Mesh._ids)}" mesh = Mesh(nodes, panels, name=f"{name}_mesh") mesh.rotate_around_center_to_align_vectors( (0, 0, 0), mesh.faces_normals[0], normal) mesh.translate(center) return mesh
def load_MSH(filename, name=None): """Loads .MSH mesh files generated by GMSH by C. Geuzaine and J.F. Remacle. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- MSH files have a 1-indexing """ import re _check_file(filename) with open(filename, 'r') as file: data = file.read() nb_nodes, nodes_data = re.search(r'\$Nodes\n(\d+)\n(.+)\$EndNodes', data, re.DOTALL).groups() nb_elts, elts_data = re.search(r'\$Elements\n(\d+)\n(.+)\$EndElements', data, re.DOTALL).groups() vertices = np.asarray(list(map(float, nodes_data.split())), dtype=np.float).reshape((-1, 4))[:, 1:] vertices = np.ascontiguousarray(vertices) faces = [] # Triangles for tri_elt in re.findall(r'(^\d+\s2(?:\s\d+)+?$)', elts_data, re.MULTILINE): tri_elt = list(map(int, tri_elt.split())) triangle = tri_elt[-3:] triangle.append(triangle[0]) faces.append(triangle) for quad_elt in re.findall(r'(^\d+\s3(?:\s\d+)+?$)', elts_data, re.MULTILINE): quad_elt = list(map(int, quad_elt.split())) quadrangle = quad_elt[-4:] faces.append(quadrangle) faces = np.asarray(faces, dtype=np.int) - 1 return Mesh(vertices, faces, name)
def extract_faces(self, id_faces_to_extract, return_index=False): """Create a new FloatingBody by extracting some faces from the mesh. The dofs evolve accordingly. """ if isinstance(self.mesh, CollectionOfMeshes): raise NotImplementedError # TODO if return_index: new_mesh, id_v = Mesh.extract_faces(self.mesh, id_faces_to_extract, return_index) else: new_mesh = Mesh.extract_faces(self.mesh, id_faces_to_extract, return_index) new_body = FloatingBody(new_mesh) LOG.info(f"Extract floating body from {self.name}.") new_body.dofs = {} for name, dof in self.dofs.items(): new_body.dofs[name] = dof[id_faces_to_extract, :] if return_index: return new_body, id_v else: return new_body
def load_STL(filename, name=None): """Loads STL file format. It relies on the reader from the VTK library. As STL file format maintains a redundant set of vertices for each faces of the mesh, it returns a merged list of nodes and connectivity array by using the merge_duplicates function. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- STL files have a 0-indexing """ vtk = import_optional_dependency("vtk") from capytaine.meshes.quality import merge_duplicate_rows _check_file(filename) reader = vtk.vtkSTLReader() reader.SetFileName(filename) reader.Update() data = reader.GetOutputDataObject(0) nv = data.GetNumberOfPoints() vertices = np.zeros((nv, 3), dtype=float) for k in range(nv): vertices[k] = np.array(data.GetPoint(k)) nf = data.GetNumberOfCells() faces = np.zeros((nf, 4), dtype=int) for k in range(nf): cell = data.GetCell(k) if cell is not None: for l in range(3): faces[k][l] = cell.GetPointId(l) faces[k][3] = faces[k][ 0] # always repeating the first node as stl is triangle only # Merging duplicates nodes vertices, new_id = merge_duplicate_rows(vertices) faces = new_id[faces] return Mesh(vertices, faces, name)
def load_RAD(filename, name=None): """Loads RADIOSS mesh files. This export file format may be chosen in ICEM meshing program. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- RAD files have a 1-indexing """ import re _check_file(filename) ifile = open(filename, 'r') data = ifile.read() ifile.close() # node_line = r'\s*\d+(?:\s*' + real_str + '){3}' node_line = r'\s*\d+\s*(' + real_str + r')\s*(' + real_str + r')\s*(' + real_str + ')' node_section = r'((?:' + node_line + ')+)' elem_line = r'^\s*(?:\d+\s+){6}\d+\s*[\r\n]+' elem_section = r'((?:' + elem_line + '){3,})' pattern_node_line = re.compile(node_line, re.MULTILINE) # pattern_node_line_group = re.compile(node_line, re.MULTILINE) pattern_elem_line = re.compile(elem_line, re.MULTILINE) pattern_node_section = re.compile(node_section, re.MULTILINE) pattern_elem_section = re.compile(elem_section, re.MULTILINE) vertices = [] node_section = pattern_node_section.search(data).group(1) for node in pattern_node_line.finditer(node_section): vertices.append(list(map(float, list(node.groups())))) vertices = np.asarray(vertices, dtype=float) faces = [] elem_section = pattern_elem_section.search(data).group(1) for elem in pattern_elem_line.findall(elem_section): faces.append(list(map(int, elem.strip().split()[3:]))) faces = np.asarray(faces, dtype=np.int) - 1 return Mesh(vertices, faces, name)
def test_dof(): nodes = np.array([[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]]) faces = np.array([[0, 1, 2, 3]]) body = FloatingBody(Mesh(nodes, faces), name="one_face") assert body.dofs == {} body.add_translation_dof(direction=(1.0, 0.0, 0.0), name="1") assert np.allclose(body.dofs["1"], np.array([1.0, 0.0, 0.0])) body.add_translation_dof(direction=(0.0, 1.0, 0.0), name="2") assert np.allclose(body.dofs["2"], np.array([0.0, 1.0, 0.0])) body.add_rotation_dof(Axis(vector=(0.0, 0.0, 1.0)), name="3") body.add_rotation_dof(Axis(point=(0.5, 0, 0), vector=(0.0, 0.0, 1.0)), name="4")
def merged(self, name=None) -> Mesh: """Merge the sub-meshes and return a full mesh. If the collection contains other collections, they are merged recursively. Optionally, a new name can be given to the resulting mesh.""" if name is None: name = self.name merged = Mesh(self.vertices, self.faces, name=name) merged.merge_duplicates() merged.heal_triangles() return merged
def test_as_set_of_faces(): """Test the representation of the mesh as a set of faces. Also allows to define the equality of two meshes.""" faces = cylinder.as_set_of_faces() assert isinstance(faces, frozenset) assert len(faces) == cylinder.nb_faces assert all(len(face) == 4 or len(face) == 3 for face in faces) # Each face is represented by 3 or 4 points assert all( len(vertex) == 3 for face in faces for vertex in face) # Each point is represented by 3 coordinates. assert cylinder == cylinder # The equality is defined as the equality of the set of faces. assert Mesh.from_set_of_faces( faces ) == cylinder # The mesh can be reconstructed from a set of faces.
def _generate_mesh(self): """Generate a 2D cartesian mesh.""" nodes = np.zeros(((self.nx+1)*(self.ny+1), 3), dtype=np.float) panels = np.zeros((self.nx*self.ny, 4), dtype=np.int) X = np.linspace(*self.x_range, self.nx+1) Y = np.linspace(*self.y_range, self.ny+1) for i, (x, y, z) in enumerate(product(X, Y, [0.0])): nodes[i, :] = x, y, z for k, (i, j) in enumerate(product(range(0, self.nx), range(0, self.ny))): panels[k, :] = (j+i*(self.ny+1), (j+1)+i*(self.ny+1), (j+1)+(i+1)*(self.ny+1), j+(i+1)*(self.ny+1)) return Mesh(nodes, panels, name=f"{self.name}_mesh")
def __init__(self, mesh=None, dofs=None, name=None): if mesh is None: mesh = Mesh(name="dummy_mesh") if dofs is None: dofs = {} if name is None: name = mesh.name assert isinstance(mesh, Mesh) or isinstance(mesh, CollectionOfMeshes) self.mesh = mesh self.full_body = None self.dofs = dofs self.name = name LOG.info(f"New floating body: {self.name}.")
def from_profile(profile: Union[Callable, Iterable[float]], z_range: Iterable[float]=np.linspace(-5, 0, 20), axis: Axis=Oz_axis, nphi: int=20, name=None): """Return a floating body using the axial symmetry. The shape of the body can be defined either with a function defining the profile as [f(z), 0, z] for z in z_range. Alternatively, the profile can be defined as a list of points. The number of vertices along the vertical direction is len(z_range) in the first case and profile.shape[0] in the second case. Parameters ---------- profile : function(float → float) or array(N, 3) define the shape of the body either as a function or a list of points. z_range: array(N), optional used only if the profile is defined as a function. axis : Axis symmetry axis nphi : int, optional number of vertical slices forming the body name : str, optional name of the generated body (optional) Returns ------- AxialSymmetricMesh the generated mesh """ if name is None: name = "axisymmetric_mesh" if callable(profile): z_range = np.asarray(z_range) x_values = [profile(z) for z in z_range] profile_array = np.stack([x_values, np.zeros(len(z_range)), z_range]).T else: profile_array = np.asarray(profile) assert len(profile_array.shape) == 2 assert profile_array.shape[1] == 3 n = profile_array.shape[0] angle = 2 * np.pi / nphi nodes_slice = np.concatenate([profile_array, axis.rotate_points(profile_array, angle)]) faces_slice = np.array([[i, i+n, i+n+1, i+1] for i in range(n-1)]) body_slice = Mesh(nodes_slice, faces_slice, name=f"slice_of_{name}") body_slice.merge_duplicates() body_slice.heal_triangles() return AxialSymmetricMesh(body_slice, axis=axis, nb_repetitions=nphi - 1, name=name)
def load_TEC(filename, name=None): """Loads TECPLOT (Tecplot (c)) mesh files. It relies on the tecplot file reader from the VTK library. Parameters ---------- filename: str name of the meh file on disk Returns ------- Mesh the loaded mesh Note ---- TEC files have a 1-indexing """ import re _check_file(filename) data_pattern = re.compile( r'ZONE.*\s*N\s*=\s*(\d+)\s*,\s*E=\s*(\d+)\s*,\s*F\s*=\s*FEPOINT\s*,\s*ET\s*=\s*QUADRILATERAL\s+' + r'(^(?:\s*' + real_str + r'){3,})\s+' + r'(^(?:\s*\d+)*)', re.MULTILINE) with open(filename, 'r') as f: data = f.read() nv, nf, vertices, faces = data_pattern.search(data).groups() nv = int(nv) nf = int(nf) vertices = np.asarray(list(map(float, vertices.split())), dtype=np.float).reshape((nv, -1))[:, :3] faces = np.asarray(list(map(int, faces.split())), dtype=np.int).reshape( (nf, 4)) - 1 return Mesh(vertices, faces, name)
def test_collection_of_meshes(): # Create some dummy meshes vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]], dtype=float) dummy_meshes = [Mesh(vertices, [[0, 1, 2, 3]])] for i in range(3): dummy_meshes.append(dummy_meshes[0].copy()) dummy_meshes[i + 1].translate_z(i + 1) # A first collection from a list coll = CollectionOfMeshes(dummy_meshes[:2]) assert coll.nb_submeshes == 2 assert coll.nb_vertices == 8 assert coll.nb_faces == 2 assert coll[1].nb_faces == 1 assert np.all(coll.faces_areas == np.asarray([1.0, 1.0])) assert np.all(coll.faces == np.asarray([[0, 1, 2, 3], [4, 5, 6, 7]])) assert np.all(coll.indices_of_mesh(1) == slice(1, 2)) # A copy of the collection copy_coll = coll.copy() copy_coll.translate_x(1.0) # Move assert copy_coll.nb_faces == 2 assert np.all(copy_coll.vertices[:, 0] >= 1.0) # Has moved # Another collection from an iterable other_coll = CollectionOfMeshes(iter(dummy_meshes)) assert other_coll.nb_faces == 4 assert np.all(other_coll.vertices[:, 0] <= 1.0) # Did not move # A collection of collections big_coll = CollectionOfMeshes((copy_coll, other_coll)) assert big_coll.nb_faces == 6 assert big_coll.nb_submeshes == 2 # Move one object in one of the sub-meshes copy_coll[1].translate_x(1.0) assert big_coll.vertices[:, 0].max() == 3.0 # Merging the big collection merged = big_coll.merged() assert isinstance(merged, Mesh)