Triangle Mesh#
triangle_mesh_connected_components.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10import copy
11
12if __name__ == "__main__":
13 bunny = o3d.data.BunnyMesh()
14 mesh = o3d.io.read_triangle_mesh(bunny.path)
15 mesh.compute_vertex_normals()
16
17 mesh = mesh.subdivide_midpoint(number_of_iterations=2)
18 vert = np.asarray(mesh.vertices)
19 min_vert, max_vert = vert.min(axis=0), vert.max(axis=0)
20 for _ in range(30):
21 cube = o3d.geometry.TriangleMesh.create_box()
22 cube.scale(0.005, center=cube.get_center())
23 cube.translate(
24 (
25 np.random.uniform(min_vert[0], max_vert[0]),
26 np.random.uniform(min_vert[1], max_vert[1]),
27 np.random.uniform(min_vert[2], max_vert[2]),
28 ),
29 relative=False,
30 )
31 mesh += cube
32 mesh.compute_vertex_normals()
33 print("Displaying input mesh ...")
34 o3d.visualization.draw([mesh])
35
36 print("Clustering connected triangles ...")
37 with o3d.utility.VerbosityContextManager(
38 o3d.utility.VerbosityLevel.Debug) as cm:
39 triangle_clusters, cluster_n_triangles, cluster_area = (
40 mesh.cluster_connected_triangles())
41 triangle_clusters = np.asarray(triangle_clusters)
42 cluster_n_triangles = np.asarray(cluster_n_triangles)
43 cluster_area = np.asarray(cluster_area)
44
45 print("Displaying mesh with small clusters removed ...")
46 mesh_0 = copy.deepcopy(mesh)
47 triangles_to_remove = cluster_n_triangles[triangle_clusters] < 100
48 mesh_0.remove_triangles_by_mask(triangles_to_remove)
49 o3d.visualization.draw([mesh_0])
triangle_mesh_cropping.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10import copy
11
12if __name__ == "__main__":
13 knot_mesh = o3d.data.KnotMesh()
14 mesh = o3d.io.read_triangle_mesh(knot_mesh.path)
15 mesh.compute_vertex_normals()
16 print("Displaying original mesh ...")
17 o3d.visualization.draw([mesh])
18
19 print("Displaying mesh of only the first half triangles ...")
20 mesh_cropped = copy.deepcopy(mesh)
21 mesh_cropped.triangles = o3d.utility.Vector3iVector(
22 np.asarray(mesh_cropped.triangles)[:len(mesh_cropped.triangles) //
23 2, :])
24 mesh_cropped.triangle_normals = o3d.utility.Vector3dVector(
25 np.asarray(mesh_cropped.triangle_normals)
26 [:len(mesh_cropped.triangle_normals) // 2, :])
27 print(mesh_cropped.triangles)
28 o3d.visualization.draw([mesh_cropped])
triangle_mesh_deformation.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import numpy as np
9import open3d as o3d
10import time
11import os
12import sys
13
14pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15sys.path.append(pyexample_path)
16
17import open3d_example as o3dex
18
19
20def problem0():
21 mesh = o3dex.get_plane_mesh(height=1, width=1)
22 mesh = mesh.subdivide_midpoint(3)
23 vertices = np.asarray(mesh.vertices)
24 static_ids = [
25 1, 46, 47, 48, 16, 51, 49, 50, 6, 31, 33, 32, 11, 26, 27, 25, 0, 64, 65,
26 20, 66, 68, 67, 7, 69, 71, 70, 22, 72, 74, 73, 3, 15, 44, 43, 45, 5, 41,
27 40, 42, 13, 39, 37, 38, 2, 56, 55, 19, 61, 60, 59, 8, 76, 75, 77, 23
28 ]
29 static_positions = []
30 for id in static_ids:
31 static_positions.append(vertices[id])
32 handle_ids = [4]
33 handle_positions = [vertices[4] + np.array((0, 0, 0.4))]
34
35 return mesh, static_ids + handle_ids, static_positions + handle_positions
36
37
38def problem1():
39 mesh = o3dex.get_plane_mesh(height=1, width=1)
40 mesh = mesh.subdivide_midpoint(3)
41 vertices = np.asarray(mesh.vertices)
42 static_ids = [
43 1, 46, 15, 43, 5, 40, 13, 38, 2, 56, 37, 39, 42, 41, 45, 44, 48, 47
44 ]
45 static_positions = []
46 for id in static_ids:
47 static_positions.append(vertices[id])
48 handle_ids = [21]
49 handle_positions = [vertices[21] + np.array((0, 0, 0.4))]
50
51 return mesh, static_ids + handle_ids, static_positions + handle_positions
52
53
54def problem2():
55 armadillo_data = o3d.data.ArmadilloMesh()
56 mesh = o3d.io.read_triangle_mesh(armadillo_data.path)
57 vertices = np.asarray(mesh.vertices)
58 static_ids = [idx for idx in np.where(vertices[:, 1] < -30)[0]]
59 static_positions = []
60 for id in static_ids:
61 static_positions.append(vertices[id])
62 handle_ids = [2490]
63 handle_positions = [vertices[2490] + np.array((-40, -40, -40))]
64
65 return mesh, static_ids + handle_ids, static_positions + handle_positions
66
67
68if __name__ == "__main__":
69 o3d.utility.set_verbosity_level(o3d.utility.Debug)
70
71 for mesh, constraint_ids, constraint_pos in [
72 problem0(), problem1(), problem2()
73 ]:
74 constraint_ids = np.array(constraint_ids, dtype=np.int32)
75 constraint_pos = o3d.utility.Vector3dVector(constraint_pos)
76 tic = time.time()
77 mesh_prime = mesh.deform_as_rigid_as_possible(
78 o3d.utility.IntVector(constraint_ids), constraint_pos, max_iter=50)
79 print("deform took {}[s]".format(time.time() - tic))
80 mesh_prime.compute_vertex_normals()
81
82 mesh.paint_uniform_color((1, 0, 0))
83 handles = o3d.geometry.PointCloud()
84 handles.points = constraint_pos
85 handles.paint_uniform_color((0, 1, 0))
86 o3d.visualization.draw_geometries([mesh, mesh_prime, handles])
87
88 o3d.utility.set_verbosity_level(o3d.utility.Info)
triangle_mesh_filtering_average.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8from numpy.random.mtrand import laplace
9import open3d as o3d
10import numpy as np
11
12
13def average_filtering():
14 # Create noisy mesh.
15 knot_mesh = o3d.data.KnotMesh()
16 mesh_in = o3d.io.read_triangle_mesh(knot_mesh.path)
17 vertices = np.asarray(mesh_in.vertices)
18 noise = 5
19 vertices += np.random.uniform(0, noise, size=vertices.shape)
20 mesh_in.vertices = o3d.utility.Vector3dVector(vertices)
21 mesh_in.compute_vertex_normals()
22 print("Displaying input mesh ...")
23 o3d.visualization.draw_geometries([mesh_in])
24
25 print("Displaying output of average mesh filter after 1 iteration ...")
26 mesh_out = mesh_in.filter_smooth_simple(number_of_iterations=1)
27 mesh_out.compute_vertex_normals()
28 o3d.visualization.draw_geometries([mesh_out])
29
30 print("Displaying output of average mesh filter after 5 iteration ...")
31 mesh_out = mesh_in.filter_smooth_simple(number_of_iterations=5)
32 mesh_out.compute_vertex_normals()
33 o3d.visualization.draw_geometries([mesh_out])
34
35
36def laplace_filtering():
37 # Create noisy mesh.
38 knot_mesh = o3d.data.KnotMesh()
39 mesh_in = o3d.io.read_triangle_mesh(knot_mesh.path)
40 vertices = np.asarray(mesh_in.vertices)
41 noise = 5
42 vertices += np.random.uniform(0, noise, size=vertices.shape)
43 mesh_in.vertices = o3d.utility.Vector3dVector(vertices)
44 mesh_in.compute_vertex_normals()
45 print("Displaying input mesh ...")
46 o3d.visualization.draw_geometries([mesh_in])
47
48 print("Displaying output of Laplace mesh filter after 10 iteration ...")
49 mesh_out = mesh_in.filter_smooth_laplacian(number_of_iterations=10)
50 mesh_out.compute_vertex_normals()
51 o3d.visualization.draw_geometries([mesh_out])
52
53 print("Displaying output of Laplace mesh filter after 50 iteration ...")
54 mesh_out = mesh_in.filter_smooth_laplacian(number_of_iterations=50)
55 mesh_out.compute_vertex_normals()
56 o3d.visualization.draw_geometries([mesh_out])
57
58
59def taubin_filtering():
60 # Create noisy mesh.
61 knot_mesh = o3d.data.KnotMesh()
62 mesh_in = o3d.io.read_triangle_mesh(knot_mesh.path)
63 vertices = np.asarray(mesh_in.vertices)
64 noise = 5
65 vertices += np.random.uniform(0, noise, size=vertices.shape)
66 mesh_in.vertices = o3d.utility.Vector3dVector(vertices)
67 mesh_in.compute_vertex_normals()
68 print("Displaying input mesh ...")
69 o3d.visualization.draw_geometries([mesh_in])
70
71 print("Displaying output of Taubin mesh filter after 10 iteration ...")
72 mesh_out = mesh_in.filter_smooth_taubin(number_of_iterations=10)
73 mesh_out.compute_vertex_normals()
74 o3d.visualization.draw_geometries([mesh_out])
75
76 print("Displaying output of Taubin mesh filter after 100 iteration ...")
77 mesh_out = mesh_in.filter_smooth_taubin(number_of_iterations=100)
78 mesh_out.compute_vertex_normals()
79 o3d.visualization.draw_geometries([mesh_out])
80
81
82if __name__ == "__main__":
83 average_filtering()
84 laplace_filtering()
85 taubin_filtering()
triangle_mesh_from_point_cloud_alpha_shapes.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 bunny = o3d.data.BunnyMesh()
12 mesh = o3d.io.read_triangle_mesh(bunny.path)
13 mesh.compute_vertex_normals()
14
15 pcd = mesh.sample_points_poisson_disk(750)
16 print("Displaying input pointcloud ...")
17 o3d.visualization.draw_geometries([pcd])
18 alpha = 0.03
19 print(f"alpha={alpha:.3f}")
20 print('Running alpha shapes surface reconstruction ...')
21 mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
22 pcd, alpha)
23 mesh.compute_triangle_normals(normalized=True)
24 print("Displaying reconstructed mesh ...")
25 o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True)
triangle_mesh_from_point_cloud_ball_pivoting.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 bunny = o3d.data.BunnyMesh()
12 gt_mesh = o3d.io.read_triangle_mesh(bunny.path)
13 gt_mesh.compute_vertex_normals()
14
15 pcd = gt_mesh.sample_points_poisson_disk(3000)
16 print("Displaying input pointcloud ...")
17 o3d.visualization.draw([pcd], point_size=5)
18
19 radii = [0.005, 0.01, 0.02, 0.04]
20 print('Running ball pivoting surface reconstruction ...')
21 rec_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
22 pcd, o3d.utility.DoubleVector(radii))
23 print("Displaying reconstructed mesh ...")
24 o3d.visualization.draw([rec_mesh])
triangle_mesh_from_point_cloud_poisson.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10
11if __name__ == "__main__":
12 eagle = o3d.data.EaglePointCloud()
13 pcd = o3d.io.read_point_cloud(eagle.path)
14 R = pcd.get_rotation_matrix_from_xyz((np.pi, -np.pi / 4, 0))
15 pcd.rotate(R, center=(0, 0, 0))
16 print('Displaying input pointcloud ...')
17 o3d.visualization.draw([pcd])
18
19 print('Running Poisson surface reconstruction ...')
20 mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
21 pcd, depth=9)
22 print('Displaying reconstructed mesh ...')
23 o3d.visualization.draw([mesh])
triangle_mesh_normal_estimation.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10
11if __name__ == "__main__":
12 knot_mesh = o3d.data.KnotMesh()
13 mesh = o3d.io.read_triangle_mesh(knot_mesh.path)
14 print("Displaying mesh without normals ...")
15 # Invalidate existing normals.
16 mesh.triangle_normals = o3d.utility.Vector3dVector(np.zeros((1, 3)))
17 print("normals: \n", np.asarray(mesh.triangle_normals))
18 o3d.visualization.draw([mesh])
19
20 print("Computing normals and rendering it ...")
21 mesh.compute_vertex_normals()
22 print("normals: \n", np.asarray(mesh.triangle_normals))
23 o3d.visualization.draw([mesh])
triangle_mesh_project_to_albedo.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7"""This example demonstrates project_image_to_albedo. Use create_dataset mode to
8render images of a 3D mesh or model from different viewpoints.
9albedo_from_dataset mode then uses the calibrated images to re-create the albedo
10texture for the mesh.
11"""
12import argparse
13from pathlib import Path
14import subprocess as sp
15import time
16import numpy as np
17import open3d as o3d
18from open3d.visualization import gui, rendering, O3DVisualizer
19from open3d.core import Tensor
20
21
22def download_smithsonian_baluster_vase():
23 """Download the Smithsonian Baluster Vase 3D model."""
24 vase_url = 'https://3d-api.si.edu/content/document/3d_package:d8c62634-4ebc-11ea-b77f-2e728ce88125/resources/F1980.190%E2%80%93194_baluster_vase-150k-4096.glb'
25 import urllib.request
26
27 def show_progress(block_num, block_size, total_size):
28 total_size = total_size >> 20 if total_size > 0 else "??" # Convert to MB if known
29 print(
30 "Downloading F1980_baluster_vase.glb... "
31 f"{(block_num * block_size) >>20}MB / {total_size}MB",
32 end="\r")
33
34 urllib.request.urlretrieve(vase_url,
35 filename="F1980_baluster_vase.glb",
36 reporthook=show_progress)
37 print("\nDownload complete.")
38
39
40def create_dataset(meshfile, n_images=10, movie=False, vary_exposure=False):
41 """Render images of a 3D mesh from different viewpoints, covering the
42 northern hemisphere. These form a synthetic dataset to test the
43 project_images_to_albedo function.
44 """
45 # Adjust these parameters to properly frame your model.
46 # Window system pixel scaling (e.g. 1 for normal, 2 for HiDPI / retina display)
47 SCALING = 2
48 width, height = 1024, 1024 # image width, height
49 focal_length = 512
50 d_camera_obj = 0.3 # distance from camera to object
51 K = np.array([[focal_length, 0, width / 2], [0, focal_length, height / 2],
52 [0, 0, 1]])
53 t = np.array([0, 0, d_camera_obj]) # origin / object in camera ref frame
54
55 model = o3d.io.read_triangle_model(meshfile)
56 # DefaultLit shader will produce non-uniform images with specular
57 # highlights, etc. These should be avoided to accurately capture the diffuse
58 # albedo
59 unlit = rendering.MaterialRecord()
60 unlit.shader = "unlit"
61
62 def triangle_wave(n, period=1):
63 """Triangle wave function between [0,1] with given period."""
64 return abs(n % period - period / 2) / (period / 2)
65
66 def rotate_camera_and_shoot(o3dvis):
67 Rts = []
68 images = []
69 o3dvis.scene.scene.enable_sun_light(False)
70 print("Rendering images: ", end='', flush=True)
71 n_0 = 2 * n_images // 3
72 n_1 = n_images - n_0 - 1
73 for n in range(n_images):
74 Rt = np.eye(4)
75 Rt[:3, 3] = t
76 if n < n_0:
77 theta = n * (2 * np.pi) / n_0
78 Rt[:3, :
79 3] = o3d.geometry.Geometry3D.get_rotation_matrix_from_zyx(
80 [np.pi, theta, 0])
81 elif n < n_images - 1:
82 theta = (n - n_0) * (2 * np.pi) / n_1
83 Rt[:3, :
84 3] = o3d.geometry.Geometry3D.get_rotation_matrix_from_xyz(
85 [np.pi / 4, theta, np.pi])
86 else: # one image from the top
87 Rt[:3, :
88 3] = o3d.geometry.Geometry3D.get_rotation_matrix_from_zyx(
89 [np.pi, 0, -np.pi / 2])
90 Rts.append(Rt)
91 o3dvis.setup_camera(K, Rt, width, height)
92 # Vary IBL intensity as a poxy for exposure value. IBL ranges from
93 # [0,150000]. We vary it between 20000 and 100000.
94 if vary_exposure:
95 o3dvis.set_ibl_intensity(20000 +
96 80000 * triangle_wave(n, n_images / 4))
97 o3dvis.post_redraw()
98 o3dvis.export_current_image(f"render-{n:02}.jpg")
99 images.append(f"render-{n:02}.jpg")
100 print('.', end='', flush=True)
101 np.savez("cameras.npz",
102 width=width,
103 height=height,
104 K=K,
105 Rts=Rts,
106 images=images)
107 # Now create a movie from the saved images by calling ffmpeg with
108 # subprocess
109 if movie:
110 print("\nCreating movie...", end='', flush=True)
111 sp.run([
112 "ffmpeg", "-framerate", f"{n_images/6}", "-pattern_type",
113 "glob", "-i", "render-*.jpg", "-y", meshfile.stem + ".mp4"
114 ],
115 check=True)
116 o3dvis.close()
117 print("\nDone.")
118
119 print("If the object is properly framed in the GUI window, click on the "
120 "'Save Images' action in the menu.")
121 o3d.visualization.draw([{
122 'geometry': model,
123 'name': meshfile.name,
124 'material': unlit
125 }],
126 show_ui=False,
127 width=int(width / SCALING),
128 height=int(height / SCALING),
129 actions=[("Save Images", rotate_camera_and_shoot)])
130
131
132def albedo_from_images(meshfile, calib_data_file, albedo_contrast=1.25):
133
134 model = o3d.io.read_triangle_model(meshfile)
135 tmeshes = o3d.t.geometry.TriangleMesh.from_triangle_mesh_model(model)
136 tmeshes = list(tmeshes.values())
137 calib = np.load(calib_data_file)
138 Ks = list(Tensor(calib["K"]) for _ in range(len(calib["Rts"])))
139 Rts = list(Tensor(Rt) for Rt in calib["Rts"])
140 images = list(o3d.t.io.read_image(imfile) for imfile in calib["images"])
141 calib.close()
142 start = time.time()
143 with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug):
144 albedo = tmeshes[0].project_images_to_albedo(images, Ks, Rts, 1024,
145 True)
146 albedo = albedo.linear_transform(scale=albedo_contrast) # brighten albedo
147 tmeshes[0].material.texture_maps["albedo"] = albedo
148 print(f"project_images_to_albedo ran in {time.time()-start:.2f}s")
149 o3d.t.io.write_image("albedo.png", albedo)
150 o3d.t.io.write_triangle_mesh(meshfile.stem + "_albedo.glb", tmeshes[0])
151 cam_vis = list({
152 "name":
153 f"camera-{i:02}",
154 "geometry":
155 o3d.geometry.LineSet.create_camera_visualization(
156 images[0].columns, images[0].rows, K.numpy(), Rt.numpy(), 0.1)
157 } for i, (K, Rt) in enumerate(zip(Ks, Rts)))
158 o3d.visualization.draw(cam_vis + [{
159 "name": meshfile.name,
160 "geometry": tmeshes[0]
161 }],
162 show_ui=True)
163
164
165if __name__ == "__main__":
166
167 parser = argparse.ArgumentParser(description=__doc__)
168 parser.add_argument("action",
169 choices=('create_dataset', 'albedo_from_images'))
170 parser.add_argument("--meshfile",
171 type=Path,
172 default=".",
173 help="Path to mesh file.")
174 parser.add_argument("--n-images",
175 type=int,
176 default=10,
177 help="Number of images to render.")
178 parser.add_argument("--download_sample_model",
179 help="Download a sample 3D model for this example.",
180 action="store_true")
181 parser.add_argument(
182 "--movie",
183 action="store_true",
184 help=
185 "Create movie from rendered images with ffmpeg. ffmpeg must be installed and in path."
186 )
187 args = parser.parse_args()
188
189 if args.action == "create_dataset":
190 if args.download_sample_model:
191 download_smithsonian_baluster_vase()
192 args.meshfile = "F1980_baluster_vase.glb"
193 if args.meshfile == Path("."):
194 parser.error("Please provide a path to a mesh file, or use "
195 "--download_sample_model.")
196 if args.n_images < 10:
197 parser.error("Atleast 10 images should be used!")
198 create_dataset(args.meshfile,
199 n_images=args.n_images,
200 movie=args.movie,
201 vary_exposure=True)
202 else:
203 albedo_from_images(args.meshfile, "cameras.npz")
triangle_mesh_properties.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10import os
11import sys
12
13pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14sys.path.append(pyexample_path)
15
16import open3d_example as o3dex
17
18
19def check_properties(name, mesh):
20 mesh.compute_vertex_normals()
21
22 edge_manifold = mesh.is_edge_manifold(allow_boundary_edges=True)
23 edge_manifold_boundary = mesh.is_edge_manifold(allow_boundary_edges=False)
24 vertex_manifold = mesh.is_vertex_manifold()
25 self_intersecting = mesh.is_self_intersecting()
26 watertight = mesh.is_watertight()
27 orientable = mesh.is_orientable()
28
29 print(name)
30 print(f" edge_manifold: {edge_manifold}")
31 print(f" edge_manifold_boundary: {edge_manifold_boundary}")
32 print(f" vertex_manifold: {vertex_manifold}")
33 print(f" self_intersecting: {self_intersecting}")
34 print(f" watertight: {watertight}")
35 print(f" orientable: {orientable}")
36
37 geoms = [mesh]
38 if not edge_manifold:
39 edges = mesh.get_non_manifold_edges(allow_boundary_edges=True)
40 geoms.append(o3dex.edges_to_lineset(mesh, edges, (1, 0, 0)))
41 if not edge_manifold_boundary:
42 edges = mesh.get_non_manifold_edges(allow_boundary_edges=False)
43 geoms.append(o3dex.edges_to_lineset(mesh, edges, (0, 1, 0)))
44 if not vertex_manifold:
45 verts = np.asarray(mesh.get_non_manifold_vertices())
46 pcl = o3d.geometry.PointCloud(
47 points=o3d.utility.Vector3dVector(np.asarray(mesh.vertices)[verts]))
48 pcl.paint_uniform_color((0, 0, 1))
49 geoms.append(pcl)
50 if self_intersecting:
51 intersecting_triangles = np.asarray(
52 mesh.get_self_intersecting_triangles())
53 intersecting_triangles = intersecting_triangles[0:1]
54 intersecting_triangles = np.unique(intersecting_triangles)
55 print(" # visualize self-intersecting triangles")
56 triangles = np.asarray(mesh.triangles)[intersecting_triangles]
57 edges = [
58 np.vstack((triangles[:, i], triangles[:, j]))
59 for i, j in [(0, 1), (1, 2), (2, 0)]
60 ]
61 edges = np.hstack(edges).T
62 edges = o3d.utility.Vector2iVector(edges)
63 geoms.append(o3dex.edges_to_lineset(mesh, edges, (1, 0, 1)))
64 o3d.visualization.draw_geometries(geoms, mesh_show_back_face=True)
65
66
67if __name__ == "__main__":
68 knot_mesh = o3d.data.KnotMesh()
69 mesh = o3d.io.read_triangle_mesh(knot_mesh.path)
70 check_properties('KnotMesh', mesh)
71 check_properties('Mobius',
72 o3d.geometry.TriangleMesh.create_mobius(twists=1))
73 check_properties("non-manifold edge", o3dex.get_non_manifold_edge_mesh())
74 check_properties("non-manifold vertex",
75 o3dex.get_non_manifold_vertex_mesh())
76 check_properties("open box", o3dex.get_open_box_mesh())
77 check_properties("intersecting_boxes", o3dex.get_intersecting_boxes_mesh())
triangle_mesh_sampling.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 bunny = o3d.data.BunnyMesh()
12 mesh = o3d.io.read_triangle_mesh(bunny.path)
13 mesh.compute_vertex_normals()
14
15 print("Displaying input mesh ...")
16 o3d.visualization.draw([mesh])
17
18 print("Displaying pointcloud using uniform sampling ...")
19 pcd = mesh.sample_points_uniformly(number_of_points=1000)
20 o3d.visualization.draw([pcd], point_size=5)
21
22 print("Displaying pointcloud using Poisson disk sampling ...")
23 pcd = mesh.sample_points_poisson_disk(number_of_points=1000, init_factor=5)
24 o3d.visualization.draw([pcd], point_size=5)
triangle_mesh_simplification_decimation.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 bunny = o3d.data.BunnyMesh()
12 mesh_in = o3d.io.read_triangle_mesh(bunny.path)
13 mesh_in.compute_vertex_normals()
14
15 print("Before Simplification: ", mesh_in)
16 o3d.visualization.draw_geometries([mesh_in])
17
18 mesh_smp = mesh_in.simplify_quadric_decimation(
19 target_number_of_triangles=6500)
20 print("After Simplification target number of triangles = 6500:\n", mesh_smp)
21 o3d.visualization.draw_geometries([mesh_smp])
22
23 mesh_smp = mesh_in.simplify_quadric_decimation(
24 target_number_of_triangles=1700)
25 print("After Simplification target number of triangles = 1700:\n", mesh_smp)
26 o3d.visualization.draw_geometries([mesh_smp])
triangle_mesh_simplification_vertex_clustering.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 bunny = o3d.data.BunnyMesh()
12 mesh_in = o3d.io.read_triangle_mesh(bunny.path)
13 mesh_in.compute_vertex_normals()
14
15 print("Before Simplification: ", mesh_in)
16 o3d.visualization.draw_geometries([mesh_in])
17
18 voxel_size = max(mesh_in.get_max_bound() - mesh_in.get_min_bound()) / 32
19 mesh_smp = mesh_in.simplify_vertex_clustering(
20 voxel_size=voxel_size,
21 contraction=o3d.geometry.SimplificationContraction.Average)
22 print("After Simplification with voxel size =", voxel_size, ":\n", mesh_smp)
23 o3d.visualization.draw_geometries([mesh_smp])
24
25 voxel_size = max(mesh_in.get_max_bound() - mesh_in.get_min_bound()) / 16
26 mesh_smp = mesh_in.simplify_vertex_clustering(
27 voxel_size=voxel_size,
28 contraction=o3d.geometry.SimplificationContraction.Average)
29 print("After Simplification with voxel size =", voxel_size, ":\n", mesh_smp)
30 o3d.visualization.draw_geometries([mesh_smp])
triangle_mesh_subdivision.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9
10if __name__ == "__main__":
11 knot_mesh = o3d.data.KnotMesh()
12 mesh = o3d.io.read_triangle_mesh(knot_mesh.path)
13 mesh.compute_vertex_normals()
14 print("Before Subdivision: ", mesh)
15 print("Displaying input mesh ...")
16 o3d.visualization.draw_geometries([mesh], mesh_show_wireframe=True)
17 mesh = mesh.subdivide_loop(number_of_iterations=1)
18 print("After Subdivision: ", mesh)
19 print("Displaying subdivided mesh ...")
20 o3d.visualization.draw_geometries([mesh], mesh_show_wireframe=True)
triangle_mesh_transformation.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10import copy
11
12
13def translate():
14 mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()
15 mesh_tx = copy.deepcopy(mesh).translate((1.3, 0, 0))
16 mesh_ty = copy.deepcopy(mesh).translate((0, 1.3, 0))
17 print('Displaying original and translated geometries ...')
18 o3d.visualization.draw([{
19 "name": "Original Geometry",
20 "geometry": mesh
21 }, {
22 "name": "Translated (in X) Geometry",
23 "geometry": mesh_tx
24 }, {
25 "name": "Translated (in Y) Geometry",
26 "geometry": mesh_ty
27 }],
28 show_ui=True)
29
30
31def rotate():
32 mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()
33 mesh_r = copy.deepcopy(mesh)
34 R = mesh.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4))
35 mesh_r.rotate(R, center=(0, 0, 0))
36 print('Displaying original and rotated geometries ...')
37 o3d.visualization.draw([{
38 "name": "Original Geometry",
39 "geometry": mesh
40 }, {
41 "name": "Rotated Geometry",
42 "geometry": mesh_r
43 }],
44 show_ui=True)
45
46
47def scale():
48 mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()
49 mesh_s = copy.deepcopy(mesh).translate((2, 0, 0))
50 mesh_s.scale(0.5, center=mesh_s.get_center())
51 print('Displaying original and scaled geometries ...')
52 o3d.visualization.draw([{
53 "name": "Original Geometry",
54 "geometry": mesh
55 }, {
56 "name": "Scaled Geometry",
57 "geometry": mesh_s
58 }],
59 show_ui=True)
60
61
62def transform():
63 mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()
64 T = np.eye(4)
65 T[:3, :3] = mesh.get_rotation_matrix_from_xyz((0, np.pi / 3, np.pi / 2))
66 T[0, 3] = 1
67 T[1, 3] = 1.3
68 print(T)
69 mesh_t = copy.deepcopy(mesh).transform(T)
70 print('Displaying original and transformed geometries ...')
71 o3d.visualization.draw([{
72 "name": "Original Geometry",
73 "geometry": mesh
74 }, {
75 "name": "Transformed Geometry",
76 "geometry": mesh_t
77 }],
78 show_ui=True)
79
80
81if __name__ == "__main__":
82
83 translate()
84 rotate()
85 scale()
86 transform()
triangle_mesh_with_numpy.py#
1# ----------------------------------------------------------------------------
2# - Open3D: www.open3d.org -
3# ----------------------------------------------------------------------------
4# Copyright (c) 2018-2024 www.open3d.org
5# SPDX-License-Identifier: MIT
6# ----------------------------------------------------------------------------
7
8import open3d as o3d
9import numpy as np
10
11if __name__ == "__main__":
12 # Read a mesh and get its data as numpy arrays.
13 knot_mesh = o3d.data.KnotMesh()
14 mesh = o3d.io.read_triangle_mesh(knot_mesh.path)
15 mesh.paint_uniform_color([0.5, 0.1, 0.3])
16 print('Vertices:')
17 print(np.asarray(mesh.vertices))
18 print('Vertex Colors:')
19 print(np.asarray(mesh.vertex_colors))
20 print('Vertex Normals:')
21 print(np.asarray(mesh.vertex_normals))
22 print('Triangles:')
23 print(np.asarray(mesh.triangles))
24 print('Triangle Normals:')
25 print(np.asarray(mesh.triangle_normals))
26 print("Displaying mesh ...")
27 print(mesh)
28 o3d.visualization.draw([mesh])
29
30 # Create a mesh using numpy arrays with random colors.
31 N = 5
32 vertices = o3d.utility.Vector3dVector(
33 np.array([[0, 0, 0], [1, 0, 0], [1, 0, 1], [0, 0, 1], [0.5, 0.5, 0.5]]))
34 triangles = o3d.utility.Vector3iVector(
35 np.array([[0, 1, 2], [0, 2, 3], [0, 4, 1], [1, 4, 2], [2, 4, 3],
36 [3, 4, 0]]))
37 mesh_np = o3d.geometry.TriangleMesh(vertices, triangles)
38 mesh_np.vertex_colors = o3d.utility.Vector3dVector(
39 np.random.uniform(0, 1, size=(N, 3)))
40 mesh_np.compute_vertex_normals()
41 print(np.asarray(mesh_np.triangle_normals))
42 print("Displaying mesh made using numpy ...")
43 o3d.visualization.draw_geometries([mesh_np])