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 _generate_mesh_with_reflection_symmetry(self, resolution, top, bottom, name): width, thickness, height = self.size nw, nth, nh = resolution half_front = Rectangle.generate_rectangle_mesh( width=width / 2, height=height, nw=nw // 2, nh=nh, center=(-width / 4, thickness / 2, 0), normal=(0, 1, 0), name=f"half_front_of_{name}_mesh") quarter_of_top = Rectangle.generate_rectangle_mesh( width=thickness / 2, height=width / 2, nw=nth // 2, nh=nw // 2, center=(-width / 4, thickness / 4, height / 2), normal=(0, 0, 1), name=f"top_panel_of_{name}_mesh") quarter_of_bottom = quarter_of_top.mirror( plane=xOy_Plane, inplace=False, name=f"bottom_panel_of_{name}_mesh") half_side = Rectangle.generate_rectangle_mesh( width=thickness / 2, height=height, nw=nth // 2, nh=nh, center=(-width / 2, thickness / 4, 0), normal=(1, 0, 0), name=f"half_side_of_{name}_mesh") panels = [half_front, half_side] if top: panels.append(quarter_of_top) if bottom: panels.append(quarter_of_bottom) quarter_of_mesh = CollectionOfMeshes( panels, name=f"quarter_of_{name}_mesh").merged() half_mesh = ReflectionSymmetricMesh(quarter_of_mesh, plane=yOz_Plane, name=f"half_of_{name}_mesh") return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=f"{name}_mesh")
def _generate_open_cylinder_mesh(self, nx, ntheta, name=None): """Open horizontal cylinder using the symmetry 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") ring = ReflectionSymmetricMesh(half_ring, plane=xOz_Plane, name=f"ring_of_{name}_mesh") if nx == 1: return ring else: return TranslationalSymmetricMesh( ring, translation=np.asarray([self.length / nx, 0.0, 0.0]), nb_repetitions=nx - 1, name=f"{name}_mesh")
def __init__(self, size=(5.0, 5.0), resolution=(5, 5), center=(0, 0, 0), normal=(1, 0, 0), translational_symmetry=False, reflection_symmetry=False, name=None): assert len(size) == 2, "Size of a rectangle should be given as a couple of values." assert all([h > 0 for h in size]), "Size of the rectangle mesh should be given as positive values." assert len(resolution) == 2, "Resolution of a rectangle should be given as a couple a values." assert all([h > 0 for h in resolution]), "Resolution of the rectangle mesh should be given as positive values." assert all([i == int(i) for i in resolution]), "Resolution of a rectangle should be given as integer values." assert len(center) == 3, "Position of the center of a rectangle should be given a 3-ple of values." self.size = np.asarray(size, dtype=np.float) width, height = self.size self.geometric_center = np.asarray(center, dtype=np.float) nw, nh = resolution if translational_symmetry and reflection_symmetry: raise NotImplementedError("Rectangle generation with both reflection and translational symmetries " "has not been implemented yet.") if translational_symmetry and nw == 1: LOG.warning("To use the translation symmetry of the mesh, " "it should have more than one panel in this direction. " "Will return a standard mesh instead.") if reflection_symmetry and nw % 2 == 1: raise ValueError("To use the reflection symmetry of the mesh, " "it should have an even number of panels in this direction.") if (reflection_symmetry or translational_symmetry) and normal[2] != 0: raise ValueError("To use the symmetry of the mesh, it should be vertical.") if name is None: name = f"rectangle_{next(Mesh._ids)}" LOG.debug(f"New rectangle body of size ({width}, {height}) and resolution ({nw}, {nh}), named {name}.") if reflection_symmetry: half_mesh = Rectangle.generate_rectangle_mesh( width=width/2, height=height, nw=nw//2, nh=nh, center=(0, -width/4, 0), name=f"half_of_{name}_mesh" ) mesh = ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=f"{name}_mesh") elif translational_symmetry and nw > 1: strip = Rectangle.generate_rectangle_mesh( width=width/nw, height=height, nw=1, nh=nh, center=(0, -width/2 + width/(2*nw), 0), name=f"strip_of_{name}_mesh" ) mesh = TranslationalSymmetricMesh(strip, translation=np.asarray([0, width/nw, 0]), nb_repetitions=int(nw)-1, name=name) else: mesh = Rectangle.generate_rectangle_mesh(width=width, height=height, nw=nw, nh=nh, name=name) mesh.rotate_around_center_to_align_vectors((0, 0, 0), mesh.faces_normals[0], normal) mesh.translate(center) FloatingBody.__init__(self, mesh=mesh, name=name)
def test_floating_sphere(depth, omega): """Comparison of the added mass and radiation damping for a heaving sphere described using several symmetries in finite and infinite depth. """ reso = 2 full_sphere = Sphere(radius=1.0, ntheta=reso, nphi=4*reso, axial_symmetry=False, clip_free_surface=True) full_sphere.add_translation_dof(direction=(0, 0, 1), name="Heave") problem = RadiationProblem(body=full_sphere, omega=omega, sea_bottom=-depth) result1 = solver_with_sym.solve(problem) half_sphere_mesh = full_sphere.mesh.extract_faces( np.where(full_sphere.mesh.faces_centers[:, 1] > 0)[0], name="half_sphere_mesh") two_halves_sphere = FloatingBody(ReflectionSymmetricMesh(half_sphere_mesh, xOz_Plane)) two_halves_sphere.add_translation_dof(direction=(0, 0, 1), name="Heave") problem = RadiationProblem(body=two_halves_sphere, omega=omega, sea_bottom=-depth) result2 = solver_with_sym.solve(problem) quarter_sphere_mesh = half_sphere_mesh.extract_faces( np.where(half_sphere_mesh.faces_centers[:, 0] > 0)[0], name="quarter_sphere_mesh") four_quarter_sphere = FloatingBody(ReflectionSymmetricMesh(ReflectionSymmetricMesh(quarter_sphere_mesh, yOz_Plane), xOz_Plane)) assert 'None' not in four_quarter_sphere.mesh.tree_view() four_quarter_sphere.add_translation_dof(direction=(0, 0, 1), name="Heave") problem = RadiationProblem(body=four_quarter_sphere, omega=omega, sea_bottom=-depth) result3 = solver_with_sym.solve(problem) clever_sphere = Sphere(radius=1.0, ntheta=reso, nphi=4*reso, axial_symmetry=True, clip_free_surface=True) clever_sphere.add_translation_dof(direction=(0, 0, 1), name="Heave") problem = RadiationProblem(body=clever_sphere, omega=omega, sea_bottom=-depth) result4 = solver_with_sym.solve(problem) # (quarter_sphere + half_sphere + full_sphere + clever_sphere).show() volume = 4/3*np.pi assert np.isclose(result1.added_masses["Heave"], result2.added_masses["Heave"], atol=1e-4*volume*problem.rho) assert np.isclose(result1.added_masses["Heave"], result3.added_masses["Heave"], atol=1e-4*volume*problem.rho) assert np.isclose(result1.added_masses["Heave"], result4.added_masses["Heave"], atol=1e-4*volume*problem.rho) assert np.isclose(result1.radiation_dampings["Heave"], result2.radiation_dampings["Heave"], atol=1e-4*volume*problem.rho) assert np.isclose(result1.radiation_dampings["Heave"], result3.radiation_dampings["Heave"], atol=1e-4*volume*problem.rho) assert np.isclose(result1.radiation_dampings["Heave"], result4.radiation_dampings["Heave"], atol=1e-4*volume*problem.rho)
def __init__(self, radius=1.0, resolution=(3, 5), center=(0, 0, 0), normal=(1, 0, 0), reflection_symmetry=False, axial_symmetry=False, name=None): assert radius > 0, "Radius of the disk mesh should be given as a positive value." assert len( resolution ) == 2, "Resolution of a disk should be given as a couple a values." assert all([ h > 0 for h in resolution ]), "Resolution of the disk mesh should be given as positive values." assert all([ i == int(i) for i in resolution ]), "Resolution of a disk should be given as integer values." assert len( center ) == 3, "Position of the center of a disk should be given a 3-ple of values." self.radius = float(radius) self.geometric_center = np.asarray(center, dtype=float) nr, ntheta = resolution if reflection_symmetry and ntheta % 2 == 1: raise ValueError( "To use the reflection symmetry of the mesh, " "it should have an even number of panels in this direction.") if reflection_symmetry and axial_symmetry: raise NotImplementedError( "Disk generators with both symmetries have not been implemented." ) if name is None: name = f"disk_{next(Mesh._ids)}" LOG.debug( f"New disk body of radius {radius} and resolution ({nr}, {ntheta}), named {name}." ) if reflection_symmetry: half_mesh = Disk.generate_disk_mesh(radius=self.radius, theta_max=np.pi / 2, nr=nr, ntheta=ntheta // 2, name=f"half_of_{name}_mesh") mesh = ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=f"{name}_mesh") elif axial_symmetry: mesh_slice = Disk.generate_disk_mesh(radius=self.radius, theta_max=np.pi / ntheta, nr=nr, ntheta=1, name=f"slice_of_{name}_mesh") mesh_slice.rotate_around_center_to_align_vectors( (0, 0, 0), e_x, e_z ) # Convoluted way to avoid a warning message in AxialSymmetry... mesh = AxialSymmetricMesh(mesh_slice, axis=Oz_axis, nb_repetitions=ntheta - 1, name=f"{name}_mesh") mesh.rotate_around_center_to_align_vectors((0, 0, 0), e_z, e_x) else: mesh = Disk.generate_disk_mesh(radius=self.radius, nr=nr, ntheta=ntheta, name=f"{name}_mesh") mesh.rotate_around_center_to_align_vectors( (0, 0, 0), mesh.faces_normals[0], normal) mesh.translate(center) FloatingBody.__init__(self, mesh=mesh, name=name)
def __init__(self, length=10.0, radius=1.0, center=(0, 0, 0), nx=10, ntheta=10, nr=2, reflection_symmetry=True, translation_symmetry=False, clever=None, name=None): self.length = length self.radius = radius self.geometric_center = np.asarray(center, dtype=float) if name is None: name = f"cylinder_{next(Mesh._ids)}" ntheta = 2 * (ntheta // 2) # Temporary fix to avoid mismatch in mesh # When symmetries are used, one needs an even number of panels. # TODO: When symmetries are not used, implement the odd case. if clever is not None: LOG.warning( "Deprecation warning: `clever` argument for HorizontalCylinder is deprecated." "Use `reflection_symmetry` and/or `translation_symmetry` instead." ) open_cylinder = self._generate_open_cylinder_mesh( nx, ntheta, reflection_symmetry=reflection_symmetry, translation_symmetry=translation_symmetry, name=f"body_of_{name}") if nr == 0: # No sides mesh = open_cylinder else: # Sides side = Disk(radius=radius, center=(-np.array([length / 2, 0, 0])), normal=(-1, 0, 0), reflection_symmetry=reflection_symmetry, resolution=(nr, ntheta), name=f"side_of_{name}").mesh other_side = side.copy(name=f"other_side_of_{name}_mesh") other_side.mirror(yOz_Plane) if reflection_symmetry: # Knit the sides into the symmetric representation of the open cylinder half_sides = CollectionOfMeshes( (side.half, other_side.half), name="half_sides_of_{name}_mesh") half_mesh = CollectionOfMeshes( (open_cylinder.half, half_sides), name="half_{name}_mesh") mesh = ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=f"{name}_mesh") else: sides = CollectionOfMeshes( (side, other_side), name="sides_of_cylinder_{name}_mesh") mesh = CollectionOfMeshes((open_cylinder, sides), name=f"{name}_mesh") if not reflection_symmetry and not translation_symmetry: mesh = mesh.merged() mesh.heal_mesh() mesh.translate(self.geometric_center) mesh.name = f"{name}_mesh" FloatingBody.__init__(self, mesh=mesh, name=name)
def symmetrized(self, plane): from capytaine.meshes.symmetric import ReflectionSymmetricMesh half = self.clipped(plane, name=f"{self.name}_half") return ReflectionSymmetricMesh(half, plane=plane, name=f"symmetrized_of_{self.name}")
def load_HST(filename, name=None): """Loads HYDROSTAR (Bureau Veritas (c)) mesh files. Parameters ---------- filename: str name of the mesh file on disk Returns ------- Mesh the loaded mesh Note ---- HST files have a 1-indexing """ _check_file(filename) with open(filename, 'r') as f: lines = f.readlines() optional_keywords = ['PROJECT', 'SYMMETRY'] not_implemented_optional_keywords = ['USER', 'REFLENGTH', 'GRAVITY', 'RHO', 'NBBODY'] vertices = [] faces = [] optional_data = {kw: None for kw in optional_keywords} current_context = None ignored_lines = [] for i_line, line in enumerate(lines): line = line.lstrip() if line == '': continue elif line.startswith("COORDINATES"): current_context = 'vertices' elif current_context == 'vertices' and line.startswith("ENDCOORDINATES"): current_context = None elif line.startswith("PANEL"): panels_type = int(line[10:]) current_context = ('panels', panels_type) elif (current_context == ('panels', 0) or current_context == ('panels', 1)) and line.startswith("ENDPANEL"): current_context = None elif current_context == 'vertices': # parse vertex coordinates numbers = line.split() if len(numbers) == 4: i_vertex, x, y, z = numbers if int(i_vertex) != len(vertices) + 1: raise ValueError( f"HST mesh reader expected the next vertex to be indexed as {len(vertices)+1}, " f"but it was actually indexed as {i_vertex} (line {i_line+1} of {filename}).") elif len(numbers) == 3: x, y, z = numbers vertices.append([x, y, z]) elif current_context == ('panels', 0): # parse face definition (no index given) numbers = line.split() if len(numbers) == 3: v1, v2, v3 = numbers v4 = v3 elif len(numbers) == 4: v1, v2, v3, v4 = numbers faces.append([v1, v2, v3, v4]) elif current_context == ('panels', 1): # parse face definition numbers = line.split() if len(numbers) == 4: i_face, v1, v2, v3 = numbers v4 = v3 elif len(numbers) == 5: i_face, v1, v2, v3, v4 = numbers if int(i_face) != len(faces) + 1: ii = len(faces) + 1 raise ValueError(f"HST mesh reader expected the next face to be indexed {ii},\n" f"but it was actually indexed with {i_face} (line {i_line+1} of file {filename}).") faces.append([v1, v2, v3, v4]) elif line.startswith("ENDFILE"): break else: for keyword in optional_data: if line.startswith(keyword): optional_data[keyword] = line[len(keyword)+1:].lstrip(':').strip() break else: ignored_lines.append((i_line+1, line)) if len(ignored_lines) > 0: formatted_ignored_lines = ["{: 4} | {}".format(i, line.strip('\n')) for (i, line) in ignored_lines] LOG.warning(f"HST mesh reader ignored the following lines from file {filename}:\n" + "\n".join(formatted_ignored_lines)) vertices = np.array(vertices, dtype=float) faces = np.array(faces, dtype=int) - 1 if name is None: name = optional_data['PROJECT'] if optional_data['SYMMETRY'] == '1': return ReflectionSymmetricMesh(Mesh(vertices, faces, f"half_of_{name}"), xOz_Plane, name) elif optional_data['SYMMETRY'] == '2': return ReflectionSymmetricMesh(ReflectionSymmetricMesh(Mesh(vertices, faces, f"quarter_of_{name}"), yOz_Plane, f"half_of_{name}"), xOz_Plane, name) else: return Mesh(vertices, faces, name)