def keep_immersed_part(self, free_surface=0.0, sea_bottom=-np.infty): """Clip the mesh with two horizontal planes corresponding with the free surface and the sea bottom.""" self.clip(Plane(normal=(0, 0, 1), point=(0, 0, free_surface))) if sea_bottom > -np.infty: self.clip(Plane(normal=(0, 0, -1), point=(0, 0, sea_bottom))) return self
def minced(self, nb_slices=(8, 8, 4)): """Experimental method decomposing the mesh as a hierarchical structure. Parameters ---------- nb_slices: Tuple[int, int, int] The number of slices in each of the x, y and z directions. Only powers of 2 are supported at the moment. Returns ------- FloatingBody """ minced_body = self.copy() # Extreme points of the mesh in each directions. x_min, x_max, y_min, y_max, z_min, z_max = self.mesh.axis_aligned_bbox sizes = [(x_min, x_max), (y_min, y_max), (z_min, z_max)] directions = [np.array(d) for d in [(1, 0, 0), (0, 1, 0), (0, 0, 1)]] def _slice_positions_at_depth(i): """Helper function. Returns a list of floats as follows: i=1 -> [1/2] i=2 -> [1/4, 3/4] i=3 -> [1/8, 3/8, 5/8, 7/8] ... """ denominator = 2**i return [ numerator / denominator for numerator in range(1, denominator, 2) ] # GENERATE ALL THE PLANES THAT WILL BE USED TO MINCE THE MESH planes = [] for direction, nb_slices_in_dir, (min_coord, max_coord) in zip( directions, nb_slices, sizes): planes_in_dir = [] depth_of_treelike_structure = int(np.log2(nb_slices_in_dir)) for i_depth in range(1, depth_of_treelike_structure + 1): planes_in_dir_at_depth = [] for relative_position in _slice_positions_at_depth(i_depth): slice_position = (min_coord + relative_position * (max_coord - min_coord)) * direction plane = Plane(normal=direction, point=slice_position) planes_in_dir_at_depth.append(plane) planes_in_dir.append(planes_in_dir_at_depth) planes.append(planes_in_dir) # SLICE THE MESH intermingled_x_y_z = chain.from_iterable(zip_longest(*planes)) for planes in intermingled_x_y_z: if planes is not None: for plane in planes: minced_body = minced_body.sliced_by_plane(plane) return minced_body
def test_bodies(): body = Sphere(name="sphere", clever=False) assert str(body) == "sphere" repr(body) assert np.allclose(body.geometric_center, (0, 0, 0)) body.add_translation_dof(name="Surge") body.add_translation_dof(name="Heave") # Extract faces body.extract_faces(np.where(body.mesh.faces_centers[:, 2] < 0)[0]) # Clipping body.keep_immersed_part(inplace=False) # Mirror of the dofs mirrored = body.mirrored(Plane(point=(1, 0, 0), normal=(1, 0, 0))) assert np.allclose(mirrored.geometric_center, np.array([2, 0, 0])) assert np.allclose(body.dofs['Surge'], -mirrored.dofs['Surge']) # Rotation of the dofs sideways = body.rotated(Axis(point=(0, 0, 0), vector=(0, 1, 0)), np.pi/2) assert np.allclose(sideways.dofs['Heave'][0], np.array([1, 0, 0])) upside_down = body.rotated(Axis(point=(0, 0, 0), vector=(0, 1, 0)), np.pi) assert np.allclose(body.dofs['Heave'], -upside_down.dofs['Heave']) # Copy of the body copy_of_body = body.copy(name="copy_of_sphere") copy_of_body.translate_x(10.0) copy_of_body.add_translation_dof(name="Heave") # Join bodies both = body.join_bodies(copy_of_body) assert set(both.dofs) == {'sphere__Surge', 'copy_of_sphere__Surge', 'sphere__Heave', 'copy_of_sphere__Heave'}
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 test_plane(): assert (0, 1, 1) in yOz_Plane assert Oy_axis in yOz_Plane assert np.allclose(Plane(normal=(1, 1, 0)).normal, (np.sqrt(2)/2, np.sqrt(2)/2, 0)) assert yOz_Plane == Plane(point=(0, 1, 1), normal=(2, 0, 0)) assert xOy_Plane.is_orthogonal_to(Oz_axis) points_in_xplus = np.random.rand(10, 3) + np.array([1.0, -0.5, -0.5]) assert np.all(yOz_Plane.distance_to_point(points_in_xplus) > 0) assert np.all(yOz_Plane.translated_x(-5.0).distance_to_point(points_in_xplus) > 0) assert not np.any(yOz_Plane.translated_x(5.0).distance_to_point(points_in_xplus) > 0) points_in_xminus = np.random.rand(10, 3) + np.array([-2.0, -0.5, -0.5]) assert np.all(yOz_Plane.distance_to_point(points_in_xminus) < 0) assert not np.any(yOz_Plane.translated_x(-5.0).distance_to_point(points_in_xminus) < 0) assert np.all(yOz_Plane.translated_x(5.0).distance_to_point(points_in_xminus) < 0)
def sliced_by_plane(self, plane: Plane): from capytaine.meshes.collections import CollectionOfMeshes faces_ids_on_one_side = np.where(plane.distance_to_point(self.faces_centers) < 0)[0] if len(faces_ids_on_one_side) == 0 or len(faces_ids_on_one_side) == self.nb_faces: return self.copy() else: mesh_part_1 = self.extract_faces(faces_ids_on_one_side) mesh_part_2 = self.extract_faces(list(set(range(self.nb_faces)) - set(faces_ids_on_one_side))) return CollectionOfMeshes([mesh_part_1, mesh_part_2], name=f"{self.name}_splitted_by_{plane}")
def test_clipper_indices(size): """Test clipped_mesh_faces_ids.""" mesh = Rectangle(size=(size, size), resolution=(size, size), center=(0, 0, 0)).mesh.merged() clipped_mesh = clip(mesh, plane=Plane(point=(0, 0, 0), normal=(0, 0, 1))) faces_ids = clipped_mesh._clipping_data['faces_ids'] assert clipped_mesh.nb_faces == len(faces_ids) assert all( norm(clipped_mesh.faces_centers[i] - mesh.faces_centers[face_id]) < 0.3 for i, face_id in enumerate(faces_ids))
def test_plane_transformations(): # TRANSLATIONS translated_plane = xOz_Plane.translate(vector=(1, 0, 0), inplace=False) assert xOz_Plane is not translated_plane assert xOz_Plane == translated_plane assert yOz_Plane.translated_x(10).rotated_y(np.pi / 8).c == 10 translated_plane = xOz_Plane.translate(vector=(0, 1, 0), inplace=False) assert translated_plane.c == 1 assert np.all(translated_plane.normal == xOz_Plane.normal) # ROTATIONS rotated_plane = xOz_Plane.rotate(Oy_axis, angle=np.pi / 12, inplace=False) assert rotated_plane == xOz_Plane.rotated(Oy_axis, angle=np.pi / 12) assert xOz_Plane is not rotated_plane assert xOz_Plane == rotated_plane rotated_plane = xOz_Plane.rotate(Ox_axis, angle=np.pi / 2, inplace=False) assert rotated_plane == xOy_Plane # MIRRORED BY ITSELF plane = Plane(normal=(1, 0, 0), point=(0.3, 0.2, 0.6)) assert plane.mirrored(plane) != plane assert plane.mirrored(plane) == Plane(normal=(-1, 0, 0), point=(0.3, 0.2, 0.6)) flipped_plane = plane.rotate(Axis(point=plane.point, vector=(0, 1, 0)), np.pi) assert flipped_plane == plane.mirror(plane)
def __init__(self, half: Union[Mesh, CollectionOfMeshes], plane: Plane, name=None): assert isinstance(half, Mesh) or isinstance(half, CollectionOfMeshes) assert isinstance(plane, Plane) assert plane.normal[2] == 0, "Only vertical reflection planes are supported in ReflectionSymmetry classes." other_half = half.mirrored(plane, name=f"mirrored_of_{str(half)}") super().__init__((half, other_half), name=name) self.plane = plane.copy() if self.name is not None: LOG.debug(f"New mirror symmetric mesh: {self.name}.") else: LOG.debug(f"New mirror symmetric mesh.")
def keep_immersed_part(self, free_surface=0.0, sea_bottom=-np.infty): """Remove the parts of the mesh above the sea bottom and below the free surface.""" self.clip(Plane(normal=(0, 0, 1), point=(0, 0, free_surface))) if sea_bottom > -np.infty: self.clip(Plane(normal=(0, 0, -1), point=(0, 0, sea_bottom))) return self
def test_mirror(): new_cylinder = cylinder.mirrored(Plane()) cylinder.mirror(Plane()) assert new_cylinder == cylinder