13 #include <unordered_map>
26 py::class_<TriangleMesh, PyGeometry<TriangleMesh>,
28 triangle_mesh(m,
"TriangleMesh",
30 A triangle mesh contains vertices and triangles. The triangle mesh class stores
31 the attribute data in key-value maps. There are two maps: the vertex attributes
32 map, and the triangle attribute map.
34 The attributes of the triangle mesh have different levels::
36 import cloudViewer as cv3d
38 device = cv3d.core.Device("CPU:0")
39 dtype_f = cv3d.core.float32
40 dtype_i = cv3d.core.int32
42 # Create an empty triangle mesh
43 # Use mesh.vertex to access the vertices' attributes
44 # Use mesh.triangle to access the triangles' attributes
45 mesh = cv3d.t.geometry.TriangleMesh(device)
47 # Default attribute: vertex.positions, triangle.indices
48 # These attributes is created by default and is required by all triangle
49 # meshes. The shape of both must be (N, 3). The device of "positions"
50 # determines the device of the triangle mesh.
51 mesh.vertex.positions = cv3d.core.Tensor([[0, 0, 0],
54 [0, 1, 1]], dtype_f, device)
55 mesh.triangle.indices = cv3d.core.Tensor([[0, 1, 2],
56 [0, 2, 3]]], dtype_i, device)
58 # Common attributes: vertex.colors , vertex.normals
59 # triangle.colors, triangle.normals
60 # Common attributes are used in built-in triangle mesh operations. The
61 # spellings must be correct. For example, if "normal" is used instead of
62 # "normals", some internal operations that expects "normals" will not work.
63 # "normals" and "colors" must have shape (N, 3) and must be on the same
64 # device as the triangle mesh.
65 mesh.vertex.normals = cv3d.core.Tensor([[0, 0, 1],
68 [1, 1, 1]], dtype_f, device)
69 mesh.vertex.colors = cv3d.core.Tensor([[0.0, 0.0, 0.0],
72 [0.3, 0.3, 0.3]], dtype_f, device)
73 mesh.triangle.normals = cv3d.core.Tensor(...)
74 mesh.triangle.colors = cv3d.core.Tensor(...)
76 # User-defined attributes
77 # You can also attach custom attributes. The value tensor must be on the
78 # same device as the triangle mesh. The are no restrictions on the shape and
80 pcd.vertex.labels = cv3d.core.Tensor(...)
81 pcd.triangle.features = cv3d.core.Tensor(...)
86 .def(py::init<const core::Device&>(),
87 "Construct an empty trianglemesh on the provided ``device`` "
88 "(default: 'CPU:0').",
90 .def(py::init<const core::Tensor&, const core::Tensor&>(),
91 "vertex_positions"_a,
"triangle_indices"_a)
94 py::detail::bind_copy_functions<TriangleMesh>(triangle_mesh);
96 triangle_mesh.def(py::pickle(
106 "Cannot unpickle TriangleMesh! Expecting a tuple "
114 "Device ({}) is not available. TriangleMesh will "
115 "be created on CPU.",
122 for (
auto& kv : vertex_attr) {
123 mesh.SetVertexAttr(kv.first, kv.second);
125 for (
auto& kv : triangle_attr) {
126 mesh.SetTriangleAttr(kv.first, kv.second);
136 triangle_mesh.def_property_readonly(
139 triangle_mesh.def_property_readonly(
145 "Transfer the triangle mesh to a specified device.",
146 "device"_a,
"copy"_a =
false);
148 "Returns copy of the triangle mesh on the same device.");
155 "Transfer the triangle mesh to CPU. If the triangle mesh "
156 "is already on CPU, no copy will be performed.");
160 return triangle_mesh.To(
core::Device(
"CUDA", device_id));
162 "Transfer the triangle mesh to a CUDA device. If the triangle mesh "
163 "is already on the specified CUDA device, no copy will be "
169 "Returns the min bound for point coordinates.");
171 "Returns the max bound for point coordinates.");
173 "Returns the center for point coordinates.");
175 "Transforms the points and normals (if exist).");
177 "relative"_a =
true,
"Translates points.");
181 "Rotate points and normals (if exist).");
185 "Normalize both triangle normals and vertex normals to length 1.");
186 triangle_mesh.def(
"compute_triangle_normals",
188 "Function to compute triangle normals, usually called "
190 "normalized"_a =
true);
191 triangle_mesh.def(
"compute_vertex_normals",
193 "Function to compute vertex normals, usually called "
195 "normalized"_a =
true);
199 R
"(Computes the surface area of the mesh, i.e., the sum of the individual triangle surfaces.
202 This computes the surface area of the Stanford Bunny::
203 bunny = cv3d.data.BunnyMesh()
204 mesh = cv3d.t.io.read_triangle_mesh(bunny.path)
205 print('The surface area is', mesh.get_surface_area())
208 A scalar describing the surface area of the mesh.
213 "joggle_inputs"_a =
false,
214 R
"(Compute the convex hull of a point cloud using qhull. This runs on the CPU.
217 joggle_inputs (bool, default=False): Handle precision problems by
218 randomly perturbing the input data. Set to True if perturbing the input
219 iis acceptable but you need convex simplicial output. If False,
220 neighboring facets may be merged in case of precision problems. See
221 `QHull docs <http://www.qhull.org/html/qh-impre.htm#joggle`__ for more
225 TriangleMesh representing the convexh hull. This contains an
226 extra vertex property "point_indices" that contains the index of the
227 corresponding vertex in the original mesh.
230 We will load the Stanford Bunny dataset, compute and display it's convex hull::
232 bunny = cv3d.data.BunnyMesh()
233 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
234 hull = mesh.compute_convex_hull()
235 cv3d.visualization.draw([{'name': 'bunny', 'geometry': mesh}, {'name': 'convex hull', 'geometry': hull}])
239 triangle_mesh.def_static(
243 "Create a TriangleMesh from a legacy CloudViewer TriangleMesh.");
244 triangle_mesh.def_static(
249 R
"(Convert a TriangleMeshModel (e.g. as read from a file with
250 `cloudViewer.io.read_triangle_mesh_model()`) to a dictionary of mesh names to
251 triangle meshes with the specified vertex and triangle dtypes and moved to the
252 specified device. Only a single material per mesh is supported. Materials common
253 to multiple meshes will be duplicated. Textures (as t.geometry.Image) will use
254 shared storage on the CPU (GPU resident images for textures is not yet supported).
257 Dictionary of names to triangle meshes.
260 Converting the FlightHelmetModel to a dictionary of triangle meshes::
262 flight_helmet = cv3d.data.FlightHelmetModel()
263 model = cv3d.io.read_triangle_model(flight_helmet.path)
264 mesh_dict = cv3d.t.geometry.TriangleMesh.from_triangle_mesh_model(model)
265 cv3d.visualization.draw(list({"name": name, "geometry": tmesh} for
266 (name, tmesh) in mesh_dict.items()))
270 "Convert to a legacy CloudViewer TriangleMesh.");
274 R
"(Returns a new triangle mesh clipped with the plane.
276 This method clips the triangle mesh with the specified plane.
277 Parts of the mesh on the positive side of the plane will be kept and triangles
278 intersected by the plane will be cut.
281 point (cloudViewer.core.Tensor): A point on the plane.
283 normal (cloudViewer.core.Tensor): The normal of the plane. The normal points to
284 the positive side of the plane for which the geometry will be kept.
287 New triangle mesh clipped with the plane.
290 This example shows how to create a hemisphere from a sphere::
292 import cloudViewer as cv3d
294 sphere = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.geometry.ccMesh.create_sphere())
295 hemisphere = sphere.clip_plane(point=[0,0,0], normal=[1,0,0])
297 cv3d.visualization.draw(hemisphere)
306 std::vector<double> cv(contour_values.begin(),
307 contour_values.end());
310 "point"_a,
"normal"_a,
"contour_values"_a = std::list<double>{0.0},
311 R
"(Returns a line set with the contour slices defined by the plane and values.
313 This method generates slices as LineSet from the mesh at specific contour
314 values with respect to a plane.
317 point (cloudViewer.core.Tensor): A point on the plane.
318 normal (cloudViewer.core.Tensor): The normal of the plane.
319 contour_values (list): A list of contour values at which slices will be
320 generated. The value describes the signed distance to the plane.
323 LineSet with the extracted contours.
326 This example shows how to create a hemisphere from a sphere::
328 import cloudViewer as cv3d
331 bunny = cv3d.data.BunnyMesh()
332 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
333 contours = mesh.slice_plane([0,0,0], [0,1,0], np.linspace(0,0.2))
334 cv3d.visualization.draw([{'name': 'bunny', 'geometry': contours}])
341 "Create a box triangle mesh. One vertex of the box "
342 "will be placed at the origin and the box aligns "
343 "with the positive x, y, and z axes.",
344 "width"_a = 1.0,
"height"_a = 1.0,
"depth"_a = 1.0,
349 "Create a sphere mesh centered at (0, 0, 0).",
350 "radius"_a = 1.0,
"resolution"_a = 20,
355 "Create a tetrahedron mesh centered at (0, 0, 0).",
360 "Create a octahedron mesh centered at (0, 0, 0).",
365 "Create a icosahedron mesh centered at (0, 0, 0).",
370 "Create a cylinder mesh.",
"radius"_a = 1.0,
371 "height"_a = 2.0,
"resolution"_a = 20,
"split"_a = 4,
376 "Create a cone mesh.",
"radius"_a = 1.0,
377 "height"_a = 2.0,
"resolution"_a = 20,
"split"_a = 1,
382 "Create a torus mesh.",
"torus_radius"_a = 1.0,
383 "tube_radius"_a = 0.5,
"radial_resolution"_a = 30,
384 "tubular_resolution"_a = 20,
389 "Create a arrow mesh.",
"cylinder_radius"_a = 1.0,
390 "cone_radius"_a = 1.5,
"cylinder_height"_a = 5.0,
391 "cone_height"_a = 4.0,
"resolution"_a = 20,
392 "cylinder_split"_a = 4,
"cone_split"_a = 1,
396 .def_static(
"create_coordinate_frame",
398 "Create a coordinate frame mesh.",
"size"_a = 1.0,
399 "origin"_a = Eigen::Vector3d(0.0, 0.0, 0.0),
404 "Create a Mobius strip.",
"length_split"_a = 70,
405 "width_split"_a = 15,
"twists"_a = 1,
"radius"_a = 1,
406 "flatness"_a = 1,
"width"_a = 1,
"scale"_a = 1,
412 m,
"TriangleMesh",
"create_box",
413 {{
"width",
"x-directional length."},
414 {
"height",
"y-directional length."},
415 {
"depth",
"z-directional length."},
416 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
417 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
418 {
"device",
"Device of the create mesh."}});
420 m,
"TriangleMesh",
"create_sphere",
421 {{
"radius",
"The radius of the sphere."},
422 {
"resolution",
"The resolution of the sphere."},
423 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
424 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
425 {
"device",
"Device of the create sphere."}});
427 m,
"TriangleMesh",
"create_tetrahedron",
428 {{
"radius",
"Distance from centroid to mesh vertices."},
429 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
430 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
431 {
"device",
"Device of the create tetrahedron."}});
433 m,
"TriangleMesh",
"create_octahedron",
434 {{
"radius",
"Distance from centroid to mesh vertices."},
435 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
436 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
437 {
"device",
"Device of the create octahedron."}});
439 m,
"TriangleMesh",
"create_icosahedron",
440 {{
"radius",
"Distance from centroid to mesh vertices."},
441 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
442 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
443 {
"device",
"Device of the create octahedron."}});
445 m,
"TriangleMesh",
"create_cylinder",
446 {{
"radius",
"The radius of the cylinder."},
448 "The height of the cylinder.The axis of the cylinder will be "
449 "from (0, 0, -height/2) to (0, 0, height/2)."},
451 " The circle will be split into ``resolution`` segments"},
452 {
"split",
"The ``height`` will be split into ``split`` segments."},
453 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
454 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
455 {
"device",
"Device of the create octahedron."}});
457 m,
"TriangleMesh",
"create_cone",
458 {{
"radius",
"The radius of the cone."},
460 "The height of the cone. The axis of the cone will be from (0, "
461 "0, 0) to (0, 0, height)."},
463 "The circle will be split into ``resolution`` segments"},
464 {
"split",
"The ``height`` will be split into ``split`` segments."},
465 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
466 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
467 {
"device",
"Device of the create octahedron."}});
469 m,
"TriangleMesh",
"create_torus",
471 "The radius from the center of the torus to the center of the "
473 {
"tube_radius",
"The radius of the torus tube."},
474 {
"radial_resolution",
475 "The number of segments along the radial direction."},
476 {
"tubular_resolution",
477 "The number of segments along the tubular direction."},
478 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
479 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
480 {
"device",
"Device of the create octahedron."}});
482 m,
"TriangleMesh",
"create_arrow",
483 {{
"cylinder_radius",
"The radius of the cylinder."},
484 {
"cone_radius",
"The radius of the cone."},
486 "The height of the cylinder. The cylinder is from (0, 0, 0) to "
487 "(0, 0, cylinder_height)"},
489 "The height of the cone. The axis of the cone will be from (0, "
490 "0, cylinder_height) to (0, 0, cylinder_height + cone_height)"},
492 "The cone will be split into ``resolution`` segments."},
494 "The ``cylinder_height`` will be split into ``cylinder_split`` "
497 "The ``cone_height`` will be split into ``cone_split`` "
499 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
500 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
501 {
"device",
"Device of the create octahedron."}});
503 m,
"TriangleMesh",
"create_coordinate_frame",
504 {{
"size",
"The size of the coordinate frame."},
505 {
"origin",
"The origin of the coordinate frame."},
506 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
507 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
508 {
"device",
"Device of the create octahedron."}});
510 m,
"TriangleMesh",
"create_mobius",
511 {{
"length_split",
"The number of segments along the Mobius strip."},
513 "The number of segments along the width of the Mobius strip."},
514 {
"twists",
"Number of twists of the Mobius strip."},
515 {
"radius",
"The radius of the Mobius strip."},
516 {
"flatness",
"Controls the flatness/height of the Mobius strip."},
517 {
"width",
"Width of the Mobius strip."},
518 {
"scale",
"Scale the complete Mobius strip."},
519 {
"float_dtype",
"Float_dtype, Float32 or Float64."},
520 {
"int_dtype",
"Int_dtype, Int32 or Int64."},
521 {
"device",
"Device of the create octahedron."}});
527 R
"(Create a triangle mesh from a text string.
530 text (str): The text for generating the mesh. ASCII characters 32-126 are
531 supported (includes alphanumeric characters and punctuation). In
532 addition the line feed '\n' is supported to start a new line.
533 depth (float): The depth of the generated mesh. If depth is 0 then a flat mesh will be generated.
534 float_dtype (cv3d.core.Dtype): Float type for the vertices. Either Float32 or Float64.
535 int_dtype (cv3d.core.Dtype): Int type for the triangle indices. Either Int32 or Int64.
536 device (cv3d.core.Device): The device for the returned mesh.
539 Text as triangle mesh.
542 This shows how to simplify the Stanford Bunny mesh::
544 import cloudViewer as cv3d
546 mesh = cv3d.t.geometry.TriangleMesh.create_text('CloudViewer', depth=1)
547 cv3d.visualization.draw([{'name': 'text', 'geometry': mesh}])
550 triangle_mesh.def_static(
551 "create_isosurfaces",
554 [](
const core::Tensor& volume, std::list<double> contour_values,
556 std::vector<double> cv(contour_values.begin(),
557 contour_values.end());
560 "volume"_a,
"contour_values"_a = std::list<double>{0.0},
562 R
"(Create a mesh from a 3D scalar field (volume) by computing the
565 This method uses the Flying Edges dual contouring method that computes the
566 isosurface similar to Marching Cubes. The center of the first voxel of the
567 volume is at the origin (0,0,0). The center of the voxel at index [z,y,x]
571 volume (cloudViewer.core.Tensor): 3D tensor with the volume.
572 contour_values (list): A list of contour values at which isosurfaces will
573 be generated. The default value is 0.
574 device (cv3d.core.Device): The device for the returned mesh.
577 A TriangleMesh with the extracted isosurfaces.
580 This example shows how to create a sphere from a volume::
582 import cloudViewer as cv3d
585 grid_coords = np.stack(np.meshgrid(*3*[np.linspace(-1,1,num=64)], indexing='ij'), axis=-1)
586 vol = 0.5 - np.linalg.norm(grid_coords, axis=-1)
587 mesh = cv3d.t.geometry.TriangleMesh.create_isosurfaces(vol)
588 cv3d.visualization.draw(mesh)
591 This example shows how to convert a mesh to a signed distance field (SDF) and back to a mesh::
593 import cloudViewer as cv3d
596 mesh1 = cv3d.t.geometry.TriangleMesh.create_torus()
597 grid_coords = np.stack(np.meshgrid(*3*[np.linspace(-2,2,num=64, dtype=np.float32)], indexing='ij'), axis=-1)
599 scene = cv3d.t.geometry.RaycastingScene()
600 scene.add_triangles(mesh1)
601 sdf = scene.compute_signed_distance(grid_coords)
602 mesh2 = cv3d.t.geometry.TriangleMesh.create_isosurfaces(sdf)
604 # Flip the triangle orientation for SDFs with negative values as "inside" and positive values as "outside"
605 mesh2.triangle.indices = mesh2.triangle.indices[:,[2,1,0]]
607 cv3d.visualization.draw(mesh2)
612 "simplify_quadric_decimation",
614 "preserve_volume"_a =
true,
615 R
"(Function to simplify mesh using Quadric Error Metric Decimation by Garland and Heckbert.
617 This function always uses the CPU device.
620 target_reduction (float): The factor of triangles to delete, i.e., setting
621 this to 0.9 will return a mesh with about 10% of the original triangle
622 count. It is not guaranteed that the target reduction factor will be
625 preserve_volume (bool): If set to True this enables volume preservation
626 which reduces the error in triangle normal direction.
629 Simplified TriangleMesh.
632 This shows how to simplify the Stanford Bunny mesh::
634 bunny = cv3d.data.BunnyMesh()
635 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
636 simplified = mesh.simplify_quadric_decimation(0.99)
637 cv3d.visualization.draw([{'name': 'bunny', 'geometry': simplified}])
642 "tolerance"_a = 1e-6,
643 R
"(Computes the mesh that encompasses the union of the volumes of two meshes.
644 Both meshes should be manifold.
646 This function always uses the CPU device.
649 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
652 tolerance (float): Threshold which determines when point distances are
656 The mesh describing the union volume.
659 This computes the union of a sphere and a cube::
661 box = cv3d.geometry.ccMesh.create_box()
662 box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
663 sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
664 sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
666 ans = box.boolean_union(sphere)
668 cv3d.visualization.draw([{'name': 'union', 'geometry': ans}])
673 "mesh"_a,
"tolerance"_a = 1e-6,
674 R
"(Computes the mesh that encompasses the intersection of the volumes of two meshes.
675 Both meshes should be manifold.
677 This function always uses the CPU device.
680 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
683 tolerance (float): Threshold which determines when point distances are
687 The mesh describing the intersection volume.
690 This computes the intersection of a sphere and a cube::
692 box = cv3d.geometry.ccMesh.create_box()
693 box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
694 sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
695 sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
697 ans = box.boolean_intersection(sphere)
699 cv3d.visualization.draw([{'name': 'intersection', 'geometry': ans}])
704 "tolerance"_a = 1e-6,
705 R
"(Computes the mesh that encompasses the volume after subtracting the volume of the second operand.
706 Both meshes should be manifold.
708 This function always uses the CPU device.
711 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
714 tolerance (float): Threshold which determines when point distances are
718 The mesh describing the difference volume.
721 This subtracts the sphere from the cube volume::
723 box = cv3d.geometry.ccMesh.create_box()
724 box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
725 sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
726 sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
728 ans = box.boolean_difference(sphere)
730 cv3d.visualization.draw([{'name': 'difference', 'geometry': ans}])
733 triangle_mesh.def("get_axis_aligned_bounding_box",
735 "Create an axis-aligned bounding box from vertex "
736 "attribute 'positions'.");
737 triangle_mesh.def(
"get_oriented_bounding_box",
739 "Create an oriented bounding box from vertex attribute "
744 R
"(Fill holes by triangulating boundary edges.
746 This function always uses the CPU device.
749 hole_size (float): This is the approximate threshold for filling holes.
750 The value describes the maximum radius of holes to be filled.
753 New mesh after filling holes.
756 Fill holes at the bottom of the Stanford Bunny mesh::
758 bunny = cv3d.data.BunnyMesh()
759 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
760 filled = mesh.fill_holes()
761 cv3d.visualization.draw([{'name': 'filled', 'geometry': ans}])
766 "gutter"_a = 1.f,
"max_stretch"_a = 1.f / 6,
767 "parallel_partitions"_a = 1,
"nthreads"_a = 0,
768 R
"(Creates an UV atlas and adds it as triangle attr 'texture_uvs' to the mesh.
770 Input meshes must be manifold for this method to work.
771 The algorithm is based on:
772 Zhou et al, "Iso-charts: Stretch-driven Mesh Parameterization using Spectral Analysis", Eurographics Symposium on Geometry Processing (2004)
773 Sander et al. "Signal-Specialized Parametrization" Europgraphics 2002
774 This function always uses the CPU device.
777 size (int): The target size of the texture (size x size). The uv coordinates
778 will still be in the range [0..1] but parameters like gutter use pixels
780 gutter (float): This is the space around the uv islands in pixels.
781 max_stretch (float): The maximum amount of stretching allowed. The parameter
782 range is [0..1] with 0 meaning no stretch allowed.
784 parallel_partitions (int): The approximate number of partitions created
785 before computing the UV atlas for parallelizing the computation.
786 Parallelization can be enabled with values > 1. Note that
787 parallelization increases the number of UV islands and can lead to results
790 nthreads (int): The number of threads used when parallel_partitions
791 is > 1. Set to 0 for automatic number of thread detection.
794 This function creates a face attribute "texture_uvs" and returns a tuple
795 with (max stretch, num_charts, num_partitions) storing the
796 actual amount of stretch, the number of created charts, and the number of
797 parallel partitions created.
800 This code creates a uv map for the Stanford Bunny mesh::
802 import cloudViewer as cv3d
803 bunny = cv3d.data.BunnyMesh()
804 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
805 mesh.compute_uvatlas()
807 # Add a wood texture and visualize
808 texture_data = cv3d.data.WoodTexture()
809 mesh.material.material_name = 'defaultLit'
810 mesh.material.texture_maps['albedo'] = cv3d.t.io.read_image(texture_data.albedo_texture_path)
811 cv3d.visualization.draw(mesh)
814 triangle_mesh.def("bake_vertex_attr_textures",
816 "vertex_attr"_a,
"margin"_a = 2.,
"fill"_a = 0.,
817 "update_material"_a =
true,
818 R
"(Bake vertex attributes into textures.
820 This function assumes a triangle attribute with name 'texture_uvs'.
821 Only float type attributes can be baked to textures.
823 This function always uses the CPU device.
826 size (int): The width and height of the texture in pixels. Only square
827 textures are supported.
829 vertex_attr (set): The vertex attributes for which textures should be
832 margin (float): The margin in pixels. The recommended value is 2. The margin
833 are additional pixels around the UV islands to avoid discontinuities.
835 fill (float): The value used for filling texels outside the UV islands.
837 update_material (bool): If true updates the material of the mesh.
838 Baking a vertex attribute with the name 'albedo' will become the albedo
839 texture in the material. Existing textures in the material will be
843 A dictionary of tensors that store the baked textures.
846 We generate a texture storing the xyz coordinates for each texel::
848 import cloudViewer as cv3d
849 from matplotlib import pyplot as plt
851 box = cv3d.geometry.ccMesh.create_box(create_uv_map=True)
852 box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
853 box.vertex['albedo'] = box.vertex.positions
855 # Initialize material and bake the 'albedo' vertex attribute to a
856 # texture. The texture will be automatically added to the material of
858 box.material.set_default_properties()
859 texture_tensors = box.bake_vertex_attr_textures(128, {'albedo'})
861 # Shows the textured cube.
862 cv3d.visualization.draw([box])
864 # Plot the tensor with the texture.
865 plt.imshow(texture_tensors['albedo'].numpy())
869 triangle_mesh.def("bake_triangle_attr_textures",
871 "triangle_attr"_a,
"margin"_a = 2.,
"fill"_a = 0.,
872 "update_material"_a =
true,
873 R
"(Bake triangle attributes into textures.
875 This function assumes a triangle attribute with name 'texture_uvs'.
877 This function always uses the CPU device.
880 size (int): The width and height of the texture in pixels. Only square
881 textures are supported.
883 triangle_attr (set): The vertex attributes for which textures should be
886 margin (float): The margin in pixels. The recommended value is 2. The margin
887 are additional pixels around the UV islands to avoid discontinuities.
889 fill (float): The value used for filling texels outside the UV islands.
891 update_material (bool): If true updates the material of the mesh.
892 Baking a vertex attribute with the name 'albedo' will become the albedo
893 texture in the material. Existing textures in the material will be
897 A dictionary of tensors that store the baked textures.
900 We generate a texture visualizing the index of the triangle to which the
903 import cloudViewer as cv3d
904 from matplotlib import pyplot as plt
906 box = cv3d.geometry.ccMesh.create_box(create_uv_map=True)
907 box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
908 # Creates a triangle attribute 'albedo' which is the triangle index
909 # multiplied by (255//12).
910 box.triangle['albedo'] = (255//12)*np.arange(box.triangle.indices.shape[0], dtype=np.uint8)
912 # Initialize material and bake the 'albedo' triangle attribute to a
913 # texture. The texture will be automatically added to the material of
915 box.material.set_default_properties()
916 texture_tensors = box.bake_triangle_attr_textures(128, {'albedo'})
918 # Shows the textured cube.
919 cv3d.visualization.draw([box])
921 # Plot the tensor with the texture.
922 plt.imshow(texture_tensors['albedo'].numpy())
926 "angle"_a,
"axis"_a,
"resolution"_a = 16,
927 "translation"_a = 0.0,
"capping"_a =
true,
928 R
"(Sweeps the triangle mesh rotationally about an axis.
930 angle (float): The rotation angle in degree.
931 axis (cloudViewer.core.Tensor): The rotation axis.
932 resolution (int): The resolution defines the number of intermediate sweeps
933 about the rotation axis.
934 translation (float): The translation along the rotation axis.
937 A triangle mesh with the result of the sweep operation.
940 This code generates a spring with a triangle cross-section::
942 import cloudViewer as cv3d
944 mesh = cv3d.t.geometry.TriangleMesh([[1,1,0], [0.7,1,0], [1,0.7,0]], [[0,1,2]])
945 spring = mesh.extrude_rotation(3*360, [0,1,0], resolution=3*16, translation=2)
946 cv3d.visualization.draw([{'name': 'spring', 'geometry': spring}])
950 "vector"_a,
"scale"_a = 1.0,
"capping"_a =
true,
951 R
"(Sweeps the line set along a direction vector.
953 vector (cloudViewer.core.Tensor): The direction vector.
954 scale (float): Scalar factor which essentially scales the direction vector.
957 A triangle mesh with the result of the sweep operation.
960 This code generates a wedge from a triangle::
962 import cloudViewer as cv3d
963 triangle = cv3d.t.geometry.TriangleMesh([[1.0,1.0,0.0], [0,1,0], [1,0,0]], [[0,1,2]])
964 wedge = triangle.extrude_linear([0,0,1])
965 cv3d.visualization.draw([{'name': 'wedge', 'geometry': wedge}])
970 R
"(Partition the mesh by recursively doing PCA.
972 This function creates a new face attribute with the name "partition_ids" storing
973 the partition id for each face.
976 max_faces (int): The maximum allowed number of faces in a partition.
981 This code partitions a mesh such that each partition contains at most 20k
984 import cloudViewer as cv3d
986 bunny = cv3d.data.BunnyMesh()
987 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
988 num_partitions = mesh.pca_partition(max_faces=20000)
990 # print the partition ids and the number of faces for each of them.
991 print(np.unique(mesh.triangle.partition_ids.numpy(), return_counts=True))
997 R
"(Returns a new mesh with the faces selected by a boolean mask.
1000 mask (cloudViewer.core.Tensor): A boolean mask with the shape (N) with N as the
1001 number of faces in the mesh.
1004 A new mesh with the selected faces. If the original mesh is empty, return an empty mesh.
1008 This code partitions the mesh using PCA and then visualized the individual
1011 import cloudViewer as cv3d
1013 bunny = cv3d.data.BunnyMesh()
1014 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
1015 num_partitions = mesh.pca_partition(max_faces=20000)
1018 for i in range(num_partitions):
1019 mask = mesh.triangle.partition_ids == i
1020 part = mesh.select_faces_by_mask(mask)
1021 part.vertex.colors = np.tile(np.random.rand(3), (part.vertex.positions.shape[0],1))
1024 cv3d.visualization.draw(parts)
1030 "copy_attributes"_a =
true,
1031 R
"(Returns a new mesh with the vertices selected according to the indices list.
1032 If an item from the indices list exceeds the max vertex number of the mesh
1033 or has a negative value, it is ignored.
1036 indices (cloudViewer.core.Tensor): An integer list of indices. Duplicates are
1037 allowed, but ignored. Signed and unsigned integral types are accepted.
1038 copy_attributes (bool): Indicates if vertex attributes (other than
1039 positions) and triangle attributes (other than indices) should be copied to
1043 A new mesh with the selected vertices and faces built from these vertices.
1044 If the original mesh is empty, return an empty mesh.
1048 This code selects the top face of a box, which has indices [2, 3, 6, 7]::
1050 import cloudViewer as cv3d
1052 box = cv3d.t.geometry.TriangleMesh.create_box()
1053 top_face = box.select_by_index([2, 3, 6, 7])
1056 triangle_mesh.def("project_images_to_albedo",
1058 "intrinsic_matrices"_a,
"extrinsic_matrices"_a,
1059 "tex_size"_a = 1024,
"update_material"_a =
true,
1060 py::call_guard<py::gil_scoped_release>(), R
"(
1061 Create an albedo for the triangle mesh using calibrated images. The triangle
1062 mesh must have texture coordinates ("texture_uvs" triangle attribute). This works
1063 by back projecting the images onto the texture surface. Overlapping images are
1064 blended together in the resulting albedo. For best results, use images captured
1065 with exposure and white balance lock to reduce the chance of seams in the output
1068 This function is only supported on the CPU.
1071 images (List[cloudViewer.t.geometry.Image]): List of images.
1072 intrinsic_matrices (List[cloudViewer.core.Tensor]): List of (3,3) intrinsic matrices describing
1074 extrinsic_matrices (List[cloudViewer.core.Tensor]): List of (4,4) extrinsic matrices describing
1075 the position and orientation of the camera.
1076 tex_size (int): Output albedo texture size. This is a square image, so
1077 only one side is needed.
1078 update_material (bool): Whether to update the material of the triangle
1079 mesh, possibly overwriting an existing albedo texture.
1082 Image with albedo texture.)");
1084 "remove_unreferenced_vertices",
1086 R
"(Removes unreferenced vertices from the mesh in-place.)");
1090 R
"(Compute triangle areas and save it as \"areas\" triangle attribute.
1097 This code computes the overall surface area of a box::
1099 import cloudViewer as cv3d
1100 box = cv3d.t.geometry.TriangleMesh.create_box()
1101 surface_area = box.compute_triangle_areas().triangle.areas.sum()
1104 triangle_mesh.def("remove_non_manifold_edges",
1106 R
"(Function that removes all non-manifold edges, by
1107 successively deleting triangles with the smallest surface
1108 area adjacent to the non-manifold edge until the number of
1109 adjacent triangles to the edge is `<= 2`.
1115 triangle_mesh.def("get_non_manifold_edges",
1117 "allow_boundary_edges"_a =
true,
1118 R
"(Returns the list consisting of non-manifold edges.)");
1122 "number_of_points"_a,
"use_triangle_normal"_a =
false,
1123 R
"(Sample points uniformly from the triangle mesh surface and return as a PointCloud. Normals and colors are interpolated from the triangle mesh. If texture_uvs and albedo are present, these are used to estimate the sampled point color, otherwise vertex colors are used, if present. During sampling, triangle areas are computed and saved in the "areas" attribute.
1126 number_of_points (int): The number of points to sample.
1127 use_triangle_normal (bool): If true, use the triangle normal as the normal of the sampled point. By default, the vertex normals are interpolated instead.
1130 Sampled point cloud, with colors and normals, if available.
1134 mesh = cv3d.t.geometry.TriangleMesh.create_box()
1135 mesh.vertex.colors = mesh.vertex.positions.clone()
1136 pcd = mesh.sample_points_uniformly(100000)
1137 cv3d.visualization.draw([mesh, pcd], point_size=5, show_ui=True, show_skybox=False)
1142 "mesh2"_a,
"metrics"_a,
"params"_a,
1143 R
"(Compute various metrics between two triangle meshes.
1145 This uses ray casting for distance computations between a sampled point cloud
1146 and a triangle mesh. Currently, Chamfer distance, Hausdorff distance and
1147 F-Score `[Knapitsch2017] <../tutorial/reference.html#Knapitsch2017>`_ are supported.
1148 The Chamfer distance is the sum of the mean distance to the nearest neighbor from
1149 the sampled surface points of the first mesh to the second mesh and vice versa.
1150 The F-Score at the fixed threshold radius is the harmonic mean of the Precision
1151 and Recall. Recall is the percentage of surface points from the first mesh that
1152 have the second mesh within the threshold radius, while Precision is the
1153 percentage of sampled points from the second mesh that have the first mesh
1154 surface within the threshold radius.
1160 \text{Chamfer Distance: } d_{CD}(X,Y) &= \frac{1}{|X|}\sum_{i \in X} || x_i - n(x_i, Y) || + \frac{1}{|Y|}\sum_{i \in Y} || y_i - n(y_i, X) ||\\
1161 \text{Hausdorff distance: } d_H(X,Y) &= \max \left\{ \max_{i \in X} || x_i - n(x_i, Y) ||, \max_{i \in Y} || y_i - n(y_i, X) || \right\}\\
1162 \text{Precision: } P(X,Y|d) &= \frac{100}{|X|} \sum_{i \in X} || x_i - n(x_i, Y) || < d \\
1163 \text{Recall: } R(X,Y|d) &= \frac{100}{|Y|} \sum_{i \in Y} || y_i - n(y_i, X) || < d \\
1164 \text{F-Score: } F(X,Y|d) &= \frac{2 P(X,Y|d) R(X,Y|d)}{P(X,Y|d) + R(X,Y|d)} \\
1167 As a side effect, the triangle areas are saved in the "areas" attribute.
1170 mesh2 (t.geometry.TriangleMesh): Other triangle mesh to compare with.
1171 metrics (Sequence[t.geometry.Metric]): List of Metrics to compute. Multiple metrics can be computed at once for efficiency.
1172 params (t.geometry.MetricParameters): This holds parameters required by different metrics.
1175 Tensor containing the requested metrics.
1179 from cloudViewer.t.geometry import TriangleMesh, Metric, MetricParameters
1180 # box is a cube with one vertex at the origin and a side length 1
1181 box1 = TriangleMesh.create_box()
1182 box2 = TriangleMesh.create_box()
1183 box2.vertex.positions *= 1.1
1185 # 3 faces of the cube are the same, and 3 are shifted up by 0.1
1186 metric_params = MetricParameters(fscore_radius=cv3d.utility.FloatVector(
1187 (0.05, 0.15)), n_sampled_points=100000)
1188 metrics = box1.compute_metrics(
1189 box2, (Metric.ChamferDistance, Metric.HausdorffDistance, Metric.FScore),
1193 np.testing.assert_allclose(metrics.cpu().numpy(), (0.1, 0.17, 50, 100),
std::string ToString() const
Returns string representation of device, e.g. "CPU:0", "CUDA:0".
bool IsAvailable() const
Returns true if the device is available.
Mix-in class for geometry types that can be visualized.
A triangle mesh contains vertices and triangles.
static TriangleMesh CreateBox(double width=1.0, double height=1.0, double depth=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh SimplifyQuadricDecimation(double target_reduction, bool preserve_volume=true) const
TriangleMesh & Translate(const core::Tensor &translation, bool relative=true)
Translates the VertexPositions of the TriangleMesh.
core::Tensor ComputeMetrics(const TriangleMesh &mesh2, std::vector< Metric > metrics={Metric::ChamferDistance}, MetricParameters params=MetricParameters()) const
TriangleMesh BooleanIntersection(const TriangleMesh &mesh, double tolerance=1e-6) const
static TriangleMesh CreateIsosurfaces(const core::Tensor &volume, const std::vector< double > contour_values={0.0}, const core::Device &device=core::Device("CPU:0"))
double GetSurfaceArea() const
Function that computes the surface area of the mesh, i.e. the sum of the individual triangle surfaces...
static TriangleMesh CreateCylinder(double radius=1.0, double height=2.0, int resolution=20, int split=4, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateTetrahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static geometry::TriangleMesh FromLegacy(const ccMesh &mesh_legacy, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh SelectFacesByMask(const core::Tensor &mask) const
TriangleMesh Clone() const
Returns copy of the triangle mesh on the same device.
const TensorMap & GetVertexAttr() const
Getter for vertex_attr_ TensorMap. Used in Pybind.
core::Tensor GetMaxBound() const
static TriangleMesh CreateIcosahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateText(const std::string &text, double depth=0.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
Image ProjectImagesToAlbedo(const std::vector< Image > &images, const std::vector< core::Tensor > &intrinsic_matrices, const std::vector< core::Tensor > &extrinsic_matrices, int tex_size=1024, bool update_material=true)
static std::unordered_map< std::string, geometry::TriangleMesh > FromTriangleMeshModel(const cloudViewer::visualization::rendering::TriangleMeshModel &model, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh RemoveUnreferencedVertices()
std::unordered_map< std::string, core::Tensor > BakeTriangleAttrTextures(int size, const std::unordered_set< std::string > &triangle_attr={}, double margin=2., double fill=0., bool update_material=true)
std::string ToString() const
Text description.
static TriangleMesh CreateArrow(double cylinder_radius=1.0, double cone_radius=1.5, double cylinder_height=5.0, double cone_height=4.0, int resolution=20, int cylinder_split=4, int cone_split=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh ClipPlane(const core::Tensor &point, const core::Tensor &normal) const
Clip mesh with a plane. This method clips the triangle mesh with the specified plane....
TriangleMesh RemoveNonManifoldEdges()
static TriangleMesh CreateMobius(int length_split=70, int width_split=15, int twists=1, double radius=1, double flatness=1, double width=1, double scale=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh ExtrudeRotation(double angle, const core::Tensor &axis, int resolution=16, double translation=0.0, bool capping=true) const
AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const
Create an axis-aligned bounding box from vertex attribute "positions".
core::Device GetDevice() const override
Returns the device of the geometry.
std::unordered_map< std::string, core::Tensor > BakeVertexAttrTextures(int size, const std::unordered_set< std::string > &vertex_attr={}, double margin=2., double fill=0., bool update_material=true)
TriangleMesh To(const core::Device &device, bool copy=false) const
core::Tensor GetNonManifoldEdges(bool allow_boundary_edges=true) const
TriangleMesh & NormalizeNormals()
Normalize both triangle normals and vertex normals to length 1.
TriangleMesh BooleanUnion(const TriangleMesh &mesh, double tolerance=1e-6) const
PointCloud SamplePointsUniformly(size_t number_of_points, bool use_triangle_normal=false)
int PCAPartition(int max_faces)
static TriangleMesh CreateCoordinateFrame(double size=1.0, const Eigen::Vector3d &origin=Eigen::Vector3d(0.0, 0.0, 0.0), core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateCone(double radius=1.0, double height=2.0, int resolution=20, int split=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
ccMesh ToLegacy() const
Convert to a legacy CloudViewer TriangleMesh.
static TriangleMesh CreateTorus(double torus_radius=1.0, double tube_radius=0.5, int radial_resolution=30, int tubular_resolution=20, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh & Scale(double scale, const core::Tensor ¢er)
Scales the VertexPositions of the TriangleMesh.
TriangleMesh FillHoles(double hole_size=1e6) const
TriangleMesh & Rotate(const core::Tensor &R, const core::Tensor ¢er)
Rotates the VertexPositions, VertexNormals and TriangleNormals (if exists).
TriangleMesh & ComputeTriangleAreas()
Function to compute triangle areas and save it as a triangle attribute "areas". Prints a warning,...
TriangleMesh ExtrudeLinear(const core::Tensor &vector, double scale=1.0, bool capping=true) const
TriangleMesh BooleanDifference(const TriangleMesh &mesh, double tolerance=1e-6) const
static TriangleMesh CreateOctahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
std::tuple< float, int, int > ComputeUVAtlas(size_t size=512, float gutter=1.0f, float max_stretch=1.f/6, int parallel_partitions=1, int nthreads=0)
TriangleMesh ComputeConvexHull(bool joggle_inputs=false) const
TriangleMesh & ComputeVertexNormals(bool normalized=true)
Function to compute vertex normals, usually called before rendering.
TriangleMesh SelectByIndex(const core::Tensor &indices, bool copy_attributes=true) const
static TriangleMesh CreateSphere(double radius=1.0, int resolution=20, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh & Transform(const core::Tensor &transformation)
Transforms the VertexPositions, VertexNormals and TriangleNormals (if exist) of the TriangleMesh.
const TensorMap & GetTriangleAttr() const
Getter for triangle_attr_ TensorMap. Used in Pybind.
TriangleMesh & ComputeTriangleNormals(bool normalized=true)
Function to compute triangle normals, usually called before rendering.
OrientedBoundingBox GetOrientedBoundingBox() const
Create an oriented bounding box from vertex attribute "positions".
core::Tensor GetCenter() const
core::Tensor GetMinBound() const
void ClassMethodDocInject(py::module &pybind_module, const std::string &class_name, const std::string &function_name, const std::unordered_map< std::string, std::string > &map_parameter_body_docs)
void pybind_trianglemesh(py::module &m)
Generic file read and write utility for python interface.