conquer3d 0.2.1__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {conquer3d-0.2.1/conquer3d.egg-info → conquer3d-0.2.4}/PKG-INFO +6 -4
- {conquer3d-0.2.1 → conquer3d-0.2.4}/README.md +4 -2
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/_C.pyi +107 -1
- conquer3d-0.2.4/conquer3d/_C.so +0 -0
- conquer3d-0.2.4/conquer3d/__init__.py +8 -0
- conquer3d-0.2.4/conquer3d/creation/__init__.py +5 -0
- conquer3d-0.2.4/conquer3d/creation/triangle_creation.py +23 -0
- conquer3d-0.2.4/conquer3d/csrc/binds/creation/triangle_creation.cpp +13 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/bvh.cpp +38 -2
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +1 -1
- conquer3d-0.2.4/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +22 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +1 -1
- conquer3d-0.2.4/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +338 -0
- conquer3d-0.2.4/conquer3d/csrc/creation/triangle_creation.h +97 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/bvh.cu +84 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/bvh.h +17 -4
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/kdtree.h +40 -33
- conquer3d-0.2.4/conquer3d/csrc/data_structure/mesh_bvh.cu +98 -0
- conquer3d-0.2.4/conquer3d/csrc/data_structure/mesh_bvh.h +24 -0
- conquer3d-0.2.4/conquer3d/csrc/data_structure/triangle_mesh.cu +397 -0
- conquer3d-0.2.4/conquer3d/csrc/data_structure/triangle_mesh.h +134 -0
- conquer3d-0.2.4/conquer3d/csrc/primitive/edge.h +31 -0
- conquer3d-0.2.4/conquer3d/csrc/primitive/triangles.h +106 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/pybind.cpp +7 -0
- conquer3d-0.2.4/conquer3d/data_structure/__init__.py +13 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4/conquer3d.egg-info}/PKG-INFO +6 -4
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/SOURCES.txt +12 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/pyproject.toml +2 -2
- {conquer3d-0.2.1 → conquer3d-0.2.4}/setup.py +1 -1
- conquer3d-0.2.1/conquer3d/_C.so +0 -0
- conquer3d-0.2.1/conquer3d/__init__.py +0 -5
- conquer3d-0.2.1/conquer3d/data_structure/__init__.py +0 -8
- {conquer3d-0.2.1 → conquer3d-0.2.4}/LICENSE +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/MANIFEST.in +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/check.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/constants.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/zcurve.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f2x2.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x1.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x3.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x4.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f4x1.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f4x4.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/maths.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/ops.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/aabb.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs.cu +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs.cu +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs.h +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/__init__.py +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/gs.py +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/pgs.py +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/dependency_links.txt +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/not-zip-safe +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/requires.txt +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/top_level.txt +0 -0
- {conquer3d-0.2.1 → conquer3d-0.2.4}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conquer3d
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Geometric Cuda Tool Box
|
|
5
5
|
Author-email: Do Hoang Khoi <khoido8899@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/KhoiDOO/conquer3d
|
|
8
8
|
Project-URL: Repository, https://github.com/KhoiDOO/conquer3d.git
|
|
9
9
|
Project-URL: Documentation, https://github.com/KhoiDOO/conquer3d#readme
|
|
@@ -90,5 +90,7 @@ series = {EGGH-HPG'12}
|
|
|
90
90
|
## Repository
|
|
91
91
|
- [cuBQL](https://github.com/NVIDIA/cuBQL)
|
|
92
92
|
- [cudaKDTree](https://github.com/ingowald/cudaKDTree)
|
|
93
|
-
- [Kaolin]
|
|
94
|
-
- [Pytorch3D]
|
|
93
|
+
- [Kaolin](https://github.com/NVIDIAGameWorks/kaolin)
|
|
94
|
+
- [Pytorch3D](https://github.com/facebookresearch/pytorch3d)
|
|
95
|
+
- [Open3D](https://www.open3d.org/)
|
|
96
|
+
- [trimesh](https://github.com/mikedh/trimesh)
|
|
@@ -66,5 +66,7 @@ series = {EGGH-HPG'12}
|
|
|
66
66
|
## Repository
|
|
67
67
|
- [cuBQL](https://github.com/NVIDIA/cuBQL)
|
|
68
68
|
- [cudaKDTree](https://github.com/ingowald/cudaKDTree)
|
|
69
|
-
- [Kaolin]
|
|
70
|
-
- [Pytorch3D]
|
|
69
|
+
- [Kaolin](https://github.com/NVIDIAGameWorks/kaolin)
|
|
70
|
+
- [Pytorch3D](https://github.com/facebookresearch/pytorch3d)
|
|
71
|
+
- [Open3D](https://www.open3d.org/)
|
|
72
|
+
- [trimesh](https://github.com/mikedh/trimesh)
|
|
@@ -4,7 +4,7 @@ Geocutool Python bindings
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
import torch
|
|
6
6
|
import typing
|
|
7
|
-
__all__: list[str] = ['BVH', 'GSBVH', 'KDTree', 'PGSBVH', 'compute_gs_aabb_func', 'compute_gs_covi_func', 'solve_gs_neighbor_mahalanobis_radius_func', 'solve_pgs_cluster_tangency_radius_func']
|
|
7
|
+
__all__: list[str] = ['BVH', 'GSBVH', 'KDTree', 'MeshBVH', 'PGSBVH', 'TriangleMesh', 'compute_gs_aabb_func', 'compute_gs_covi_func', 'create_sphere', 'solve_gs_neighbor_mahalanobis_radius_func', 'solve_pgs_cluster_tangency_radius_func']
|
|
8
8
|
class BVH:
|
|
9
9
|
def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
|
|
10
10
|
"""
|
|
@@ -14,6 +14,10 @@ class BVH:
|
|
|
14
14
|
"""
|
|
15
15
|
Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).
|
|
16
16
|
"""
|
|
17
|
+
def query_self(self) -> tuple[torch.Tensor, torch.Tensor]:
|
|
18
|
+
"""
|
|
19
|
+
Query the BVH against itself. Returns unique overlapping (query_ids, object_ids).
|
|
20
|
+
"""
|
|
17
21
|
class GSBVH(BVH):
|
|
18
22
|
def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
|
|
19
23
|
"""
|
|
@@ -40,6 +44,19 @@ class KDTree:
|
|
|
40
44
|
"""
|
|
41
45
|
Queries the KDTree for the K nearest neighbors.
|
|
42
46
|
"""
|
|
47
|
+
class MeshBVH(BVH):
|
|
48
|
+
def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Construct MeshBVH from Triangle AABBs.
|
|
51
|
+
"""
|
|
52
|
+
def get_self_intersection(self, vertices: torch.Tensor, triangles: torch.Tensor) -> torch.Tensor:
|
|
53
|
+
"""
|
|
54
|
+
Find all self-intersecting triangle pairs
|
|
55
|
+
"""
|
|
56
|
+
def is_self_intersection(self, vertices: torch.Tensor, triangles: torch.Tensor) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Check if there are any self-intersecting triangle pairs
|
|
59
|
+
"""
|
|
43
60
|
class PGSBVH(BVH):
|
|
44
61
|
def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
|
|
45
62
|
"""
|
|
@@ -53,6 +70,91 @@ class PGSBVH(BVH):
|
|
|
53
70
|
"""
|
|
54
71
|
Perform exact Broad-to-Narrow phase intersection between Voxels and PGS.
|
|
55
72
|
"""
|
|
73
|
+
class TriangleMesh:
|
|
74
|
+
def __init__(self, in_vertices: torch.Tensor, in_triangles: torch.Tensor, in_vertex_normals: torch.Tensor | None = None, in_vertex_colors: torch.Tensor | None = None) -> None:
|
|
75
|
+
...
|
|
76
|
+
def build_bvh(self) -> MeshBVH:
|
|
77
|
+
...
|
|
78
|
+
def compute_edges_to_triangle_map(self) -> None:
|
|
79
|
+
...
|
|
80
|
+
def compute_triangle_areas(self) -> None:
|
|
81
|
+
...
|
|
82
|
+
def compute_triangle_normals(self) -> None:
|
|
83
|
+
...
|
|
84
|
+
def compute_vertices_to_triangle_map(self) -> None:
|
|
85
|
+
...
|
|
86
|
+
def get_edges_to_triangle_map(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
87
|
+
...
|
|
88
|
+
def get_non_manifold_vertices(self) -> torch.Tensor:
|
|
89
|
+
...
|
|
90
|
+
def get_self_intersection(self) -> torch.Tensor:
|
|
91
|
+
...
|
|
92
|
+
def get_vertices_to_triangle_map(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
93
|
+
...
|
|
94
|
+
def is_edge_manifold(self, allow_boundary_edge: bool = True) -> bool:
|
|
95
|
+
...
|
|
96
|
+
def is_manifold(self, allow_boundary_edge: bool = True) -> bool:
|
|
97
|
+
...
|
|
98
|
+
def is_self_intersection(self) -> bool:
|
|
99
|
+
...
|
|
100
|
+
def is_vertex_manifold(self) -> bool:
|
|
101
|
+
...
|
|
102
|
+
def remove_triangles_by_mask(self, keep_mask: torch.Tensor) -> None:
|
|
103
|
+
...
|
|
104
|
+
@property
|
|
105
|
+
def bvh(self) -> MeshBVH:
|
|
106
|
+
...
|
|
107
|
+
@property
|
|
108
|
+
def edge_to_triangle_counts(self) -> torch.Tensor:
|
|
109
|
+
...
|
|
110
|
+
@property
|
|
111
|
+
def edge_to_triangle_indices(self) -> torch.Tensor:
|
|
112
|
+
...
|
|
113
|
+
@property
|
|
114
|
+
def edge_to_triangle_offsets(self) -> torch.Tensor:
|
|
115
|
+
...
|
|
116
|
+
@property
|
|
117
|
+
def edges(self) -> torch.Tensor:
|
|
118
|
+
...
|
|
119
|
+
@property
|
|
120
|
+
def euler_characteristic(self) -> int:
|
|
121
|
+
...
|
|
122
|
+
@property
|
|
123
|
+
def genus(self) -> int:
|
|
124
|
+
...
|
|
125
|
+
@property
|
|
126
|
+
def num_triangles(self) -> int:
|
|
127
|
+
...
|
|
128
|
+
@property
|
|
129
|
+
def surface_area(self) -> torch.Tensor:
|
|
130
|
+
...
|
|
131
|
+
@property
|
|
132
|
+
def triangle_areas(self) -> torch.Tensor:
|
|
133
|
+
...
|
|
134
|
+
@property
|
|
135
|
+
def triangle_normals(self) -> torch.Tensor:
|
|
136
|
+
...
|
|
137
|
+
@property
|
|
138
|
+
def triangles(self) -> torch.Tensor:
|
|
139
|
+
...
|
|
140
|
+
@property
|
|
141
|
+
def vertex_colors(self) -> torch.Tensor:
|
|
142
|
+
...
|
|
143
|
+
@property
|
|
144
|
+
def vertex_normals(self) -> torch.Tensor:
|
|
145
|
+
...
|
|
146
|
+
@property
|
|
147
|
+
def vertex_to_triangle_counts(self) -> torch.Tensor:
|
|
148
|
+
...
|
|
149
|
+
@property
|
|
150
|
+
def vertex_to_triangle_indices(self) -> torch.Tensor:
|
|
151
|
+
...
|
|
152
|
+
@property
|
|
153
|
+
def vertex_to_triangle_offsets(self) -> torch.Tensor:
|
|
154
|
+
...
|
|
155
|
+
@property
|
|
156
|
+
def vertices(self) -> torch.Tensor:
|
|
157
|
+
...
|
|
56
158
|
def compute_gs_aabb_func(means: torch.Tensor, scales: torch.Tensor, covis: torch.Tensor, isos: torch.Tensor | None, iso: float, tol: float, level: int) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
57
159
|
"""
|
|
58
160
|
Compute AABB for 3D Gaussians
|
|
@@ -61,6 +163,10 @@ def compute_gs_covi_func(means: torch.Tensor, rotations: torch.Tensor, scales: t
|
|
|
61
163
|
"""
|
|
62
164
|
Compute covariance matrices for 3D Gaussians
|
|
63
165
|
"""
|
|
166
|
+
def create_sphere(sectors: int = 32, stacks: int = 16, radius: float = 1.0) -> tuple[torch.Tensor, torch.Tensor]:
|
|
167
|
+
"""
|
|
168
|
+
Creates a UV sphere returning (vertices, triangles) tensors on the CPU.
|
|
169
|
+
"""
|
|
64
170
|
def solve_gs_neighbor_mahalanobis_radius_func(means: torch.Tensor, covis: torch.Tensor, k: int) -> torch.Tensor:
|
|
65
171
|
"""
|
|
66
172
|
Solve Gaussian Neighbor Mahalanobis Radius
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from .._C import create_sphere as create_sphere_c
|
|
5
|
+
|
|
6
|
+
def create_sphere(
|
|
7
|
+
sectors: int = 32,
|
|
8
|
+
stacks: int = 16,
|
|
9
|
+
radius: float = 1.0
|
|
10
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
11
|
+
"""
|
|
12
|
+
Creates a UV sphere returning (vertices, triangles) tensors on the CPU.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
sectors (int, optional): Number of longitudinal sectors. Defaults to 32.
|
|
16
|
+
stacks (int, optional): Number of latitudinal stacks. Defaults to 16.
|
|
17
|
+
radius (float, optional): Radius of the sphere. Defaults to 1.0.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
vertices: (N, 3) float32 tensor of vertex positions.
|
|
21
|
+
triangles: (M, 3) int32 tensor of triangle indices.
|
|
22
|
+
"""
|
|
23
|
+
return create_sphere_c(sectors, stacks, radius)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#include <torch/extension.h>
|
|
2
|
+
#include <pybind11/pybind11.h>
|
|
3
|
+
#include "../../creation/triangle_creation.h"
|
|
4
|
+
|
|
5
|
+
namespace py = pybind11;
|
|
6
|
+
|
|
7
|
+
void bind_creation_triangle_creation(py::module_& m) {
|
|
8
|
+
m.def("create_sphere", &triangle_creation::create_sphere,
|
|
9
|
+
py::arg("sectors") = 32,
|
|
10
|
+
py::arg("stacks") = 16,
|
|
11
|
+
py::arg("radius") = 1.0f,
|
|
12
|
+
"Creates a UV sphere returning (vertices, triangles) tensors on the CPU.");
|
|
13
|
+
}
|
|
@@ -120,7 +120,40 @@ std::tuple<torch::Tensor, torch::Tensor> BVH::query(
|
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
std::tuple<torch::Tensor, torch::Tensor> BVH::query_self()
|
|
124
|
+
{
|
|
125
|
+
auto options_int64 = this->aabb_mins.options().dtype(torch::kInt64);
|
|
126
|
+
|
|
127
|
+
if (this->num_objects == 0) {
|
|
128
|
+
throw std::runtime_error("Cannot query BVH with 0 objects.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
torch::Tensor out_query_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
|
|
132
|
+
torch::Tensor out_object_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
|
|
133
|
+
torch::Tensor hit_counter = torch::zeros({1}, options_int64);
|
|
134
|
+
|
|
135
|
+
bvh::query_self(
|
|
136
|
+
this->num_objects,
|
|
137
|
+
reinterpret_cast<const float3 *>(this->aabb_mins.data_ptr<float>()),
|
|
138
|
+
reinterpret_cast<const float3 *>(this->aabb_maxs.data_ptr<float>()),
|
|
139
|
+
reinterpret_cast<const int2 *>(this->bvh_children.data_ptr<int>()),
|
|
140
|
+
reinterpret_cast<const int *>(this->object_ids.data_ptr<int>()),
|
|
141
|
+
reinterpret_cast<int64_t *>(out_query_ids.data_ptr<int64_t>()),
|
|
142
|
+
reinterpret_cast<int64_t *>(out_object_ids.data_ptr<int64_t>()),
|
|
143
|
+
reinterpret_cast<int64_t *>(hit_counter.data_ptr<int64_t>()),
|
|
144
|
+
static_cast<int64_t>(BVH_MAX_CAPACITY)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
int64_t num_hits = hit_counter.item<int64_t>();
|
|
148
|
+
num_hits = std::min(num_hits, static_cast<int64_t>(BVH_MAX_CAPACITY));
|
|
149
|
+
|
|
150
|
+
return std::make_tuple(
|
|
151
|
+
out_query_ids.slice(0, 0, num_hits),
|
|
152
|
+
out_object_ids.slice(0, 0, num_hits)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
void bind_ds_bvh(py::module_& m)
|
|
124
157
|
{
|
|
125
158
|
py::class_<BVH>(m, "BVH")
|
|
126
159
|
.def(py::init<const torch::Tensor &, const torch::Tensor &>(),
|
|
@@ -131,5 +164,8 @@ void bind_ds_bvh(py::module &m)
|
|
|
131
164
|
.def("query", &BVH::query,
|
|
132
165
|
py::arg("query_aabb_mins"),
|
|
133
166
|
py::arg("query_aabb_maxs"),
|
|
134
|
-
"Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).")
|
|
167
|
+
"Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).")
|
|
168
|
+
|
|
169
|
+
.def("query_self", &BVH::query_self,
|
|
170
|
+
"Query the BVH against itself. Returns unique overlapping (query_ids, object_ids).");
|
|
135
171
|
}
|
|
@@ -312,7 +312,7 @@ std::tuple<torch::Tensor, torch::Tensor> GSBVH::query_edge(
|
|
|
312
312
|
return std::make_tuple(hit_mask, out_gaus_ids);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
void bind_ds_gs_bvh(py::
|
|
315
|
+
void bind_ds_gs_bvh(py::module_& m)
|
|
316
316
|
{
|
|
317
317
|
py::class_<GSBVH, BVH>(m, "GSBVH")
|
|
318
318
|
.def(py::init<const torch::Tensor &, const torch::Tensor &>(),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#include <torch/extension.h>
|
|
2
|
+
#include "../../data_structure/mesh_bvh.h"
|
|
3
|
+
#include <pybind11/pybind11.h>
|
|
4
|
+
|
|
5
|
+
namespace py = pybind11;
|
|
6
|
+
|
|
7
|
+
void bind_ds_mesh_bvh(py::module_& m)
|
|
8
|
+
{
|
|
9
|
+
py::class_<MeshBVH, BVH>(m, "MeshBVH")
|
|
10
|
+
.def(py::init<const torch::Tensor &, const torch::Tensor &>(),
|
|
11
|
+
py::arg("in_aabb_mins"),
|
|
12
|
+
py::arg("in_aabb_maxs"),
|
|
13
|
+
"Construct MeshBVH from Triangle AABBs.")
|
|
14
|
+
.def("get_self_intersection", &MeshBVH::get_self_intersection,
|
|
15
|
+
py::arg("vertices"),
|
|
16
|
+
py::arg("triangles"),
|
|
17
|
+
"Find all self-intersecting triangle pairs")
|
|
18
|
+
.def("is_self_intersection", &MeshBVH::is_self_intersection,
|
|
19
|
+
py::arg("vertices"),
|
|
20
|
+
py::arg("triangles"),
|
|
21
|
+
"Check if there are any self-intersecting triangle pairs");
|
|
22
|
+
}
|
|
@@ -222,7 +222,7 @@ std::tuple<torch::Tensor, torch::Tensor> PGSBVH::query_edge(
|
|
|
222
222
|
return std::make_tuple(hit_mask, out_gaus_ids);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
void bind_ds_pgs_bvh(py::
|
|
225
|
+
void bind_ds_pgs_bvh(py::module_& m)
|
|
226
226
|
{
|
|
227
227
|
py::class_<PGSBVH, BVH>(m, "PGSBVH")
|
|
228
228
|
.def(py::init<const torch::Tensor &, const torch::Tensor &>(),
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#include <torch/extension.h>
|
|
2
|
+
#include "../../data_structure/triangle_mesh.h"
|
|
3
|
+
#include "../../check.h"
|
|
4
|
+
|
|
5
|
+
#include <pybind11/pybind11.h>
|
|
6
|
+
#include <optional>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace py = pybind11;
|
|
10
|
+
|
|
11
|
+
#include <optional>
|
|
12
|
+
|
|
13
|
+
TriangleMesh::TriangleMesh(
|
|
14
|
+
const torch::Tensor &in_vertices,
|
|
15
|
+
const torch::Tensor &in_triangles,
|
|
16
|
+
std::optional<torch::Tensor> in_vertex_normals,
|
|
17
|
+
std::optional<torch::Tensor> in_vertex_colors)
|
|
18
|
+
{
|
|
19
|
+
CHECK_INPUT(in_vertices);
|
|
20
|
+
CHECK_INPUT(in_triangles);
|
|
21
|
+
TORCH_CHECK(in_vertices.scalar_type() == torch::kFloat32, "vertices must be float32");
|
|
22
|
+
TORCH_CHECK(in_triangles.scalar_type() == torch::kInt32, "triangles must be int32");
|
|
23
|
+
TORCH_CHECK(in_vertices.size(1) == 3, "vertices must have shape (N, 3)");
|
|
24
|
+
TORCH_CHECK(in_triangles.size(1) == 3, "triangles must have shape (M, 3)");
|
|
25
|
+
|
|
26
|
+
this->num_triangles = static_cast<uint32_t>(in_triangles.size(0));
|
|
27
|
+
|
|
28
|
+
this->vertices = in_vertices;
|
|
29
|
+
this->triangles = in_triangles;
|
|
30
|
+
|
|
31
|
+
if (in_vertex_normals.has_value() && in_vertex_normals->defined())
|
|
32
|
+
{
|
|
33
|
+
CHECK_INPUT(*in_vertex_normals);
|
|
34
|
+
TORCH_CHECK(in_vertex_normals->scalar_type() == torch::kFloat32, "vertex_normals must be float32");
|
|
35
|
+
TORCH_CHECK(in_vertex_normals->size(1) == 3, "vertex_normals must have shape (N, 3)");
|
|
36
|
+
this->vertex_normals = in_vertex_normals->clone();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (in_vertex_colors.has_value() && in_vertex_colors->defined())
|
|
40
|
+
{
|
|
41
|
+
CHECK_INPUT(*in_vertex_colors);
|
|
42
|
+
TORCH_CHECK(in_vertex_colors->scalar_type() == torch::kFloat32, "vertex_colors must be float32");
|
|
43
|
+
TORCH_CHECK(in_vertex_colors->size(1) == 3, "vertex_colors must have shape (N, 3)");
|
|
44
|
+
this->vertex_colors = in_vertex_colors->clone();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void TriangleMesh::compute_triangle_normals()
|
|
49
|
+
{
|
|
50
|
+
this->triangle_normals = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
|
|
51
|
+
triangle_mesh::compute_triangle_normals(
|
|
52
|
+
this->num_triangles,
|
|
53
|
+
reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
|
|
54
|
+
reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
|
|
55
|
+
reinterpret_cast<float3 *>(this->triangle_normals.data_ptr<float>()));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void TriangleMesh::compute_triangle_areas()
|
|
59
|
+
{
|
|
60
|
+
this->triangle_areas = torch::empty({static_cast<int64_t>(this->num_triangles)}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
|
|
61
|
+
triangle_mesh::compute_triangle_areas(
|
|
62
|
+
this->num_triangles,
|
|
63
|
+
reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
|
|
64
|
+
reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
|
|
65
|
+
reinterpret_cast<float *>(this->triangle_areas.data_ptr<float>()));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
MeshBVH TriangleMesh::build_bvh()
|
|
69
|
+
{
|
|
70
|
+
if (!this->bvh.has_value())
|
|
71
|
+
{
|
|
72
|
+
auto options = torch::TensorOptions().dtype(torch::kFloat32).device(this->vertices.device());
|
|
73
|
+
torch::Tensor aabb_mins = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, options);
|
|
74
|
+
torch::Tensor aabb_maxs = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, options);
|
|
75
|
+
|
|
76
|
+
triangle_mesh::compute_triangle_aabbs(
|
|
77
|
+
this->num_triangles,
|
|
78
|
+
reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
|
|
79
|
+
reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
|
|
80
|
+
reinterpret_cast<float3 *>(aabb_mins.data_ptr<float>()),
|
|
81
|
+
reinterpret_cast<float3 *>(aabb_maxs.data_ptr<float>()));
|
|
82
|
+
|
|
83
|
+
this->bvh = MeshBVH(aabb_mins, aabb_maxs);
|
|
84
|
+
}
|
|
85
|
+
return this->bvh.value();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
torch::Tensor TriangleMesh::get_self_intersection()
|
|
89
|
+
{
|
|
90
|
+
this->build_bvh();
|
|
91
|
+
return this->bvh.value().get_self_intersection(this->vertices, this->triangles);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
bool TriangleMesh::is_self_intersection()
|
|
95
|
+
{
|
|
96
|
+
this->build_bvh();
|
|
97
|
+
return this->bvh.value().is_self_intersection(this->vertices, this->triangles);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
torch::Tensor TriangleMesh::get_triangle_areas()
|
|
101
|
+
{
|
|
102
|
+
if (!this->triangle_areas.defined())
|
|
103
|
+
{
|
|
104
|
+
this->compute_triangle_areas();
|
|
105
|
+
}
|
|
106
|
+
return this->triangle_areas;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
torch::Tensor TriangleMesh::get_triangle_normals()
|
|
110
|
+
{
|
|
111
|
+
if (!this->triangle_normals.defined())
|
|
112
|
+
{
|
|
113
|
+
this->compute_triangle_normals();
|
|
114
|
+
}
|
|
115
|
+
return this->triangle_normals;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
torch::Tensor TriangleMesh::get_surface_area()
|
|
119
|
+
{
|
|
120
|
+
if (!this->surface_area.defined())
|
|
121
|
+
{
|
|
122
|
+
this->surface_area = this->get_triangle_areas().sum();
|
|
123
|
+
}
|
|
124
|
+
return this->surface_area;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
void TriangleMesh::compute_edges_to_triangle_map()
|
|
128
|
+
{
|
|
129
|
+
if (this->num_triangles == 0)
|
|
130
|
+
{
|
|
131
|
+
auto options_i32 = torch::TensorOptions().dtype(torch::kInt32).device(this->triangles.device());
|
|
132
|
+
this->edges = torch::empty({0, 2}, options_i32);
|
|
133
|
+
this->edge_to_triangle_offsets = torch::empty({0}, options_i32);
|
|
134
|
+
this->edge_to_triangle_counts = torch::empty({0}, options_i32);
|
|
135
|
+
this->edge_to_triangle_indices = torch::empty({0}, options_i32);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
triangle_mesh::compute_edges_to_triangle_map(
|
|
140
|
+
this->num_triangles,
|
|
141
|
+
reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
|
|
142
|
+
this->edges,
|
|
143
|
+
this->edge_to_triangle_offsets,
|
|
144
|
+
this->edge_to_triangle_counts,
|
|
145
|
+
this->edge_to_triangle_indices);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_edges_to_triangle_map()
|
|
149
|
+
{
|
|
150
|
+
if (!this->edges.defined())
|
|
151
|
+
{
|
|
152
|
+
this->compute_edges_to_triangle_map();
|
|
153
|
+
}
|
|
154
|
+
return std::make_tuple(
|
|
155
|
+
this->edges,
|
|
156
|
+
this->edge_to_triangle_offsets,
|
|
157
|
+
this->edge_to_triangle_counts,
|
|
158
|
+
this->edge_to_triangle_indices);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
torch::Tensor TriangleMesh::get_edges()
|
|
162
|
+
{
|
|
163
|
+
if (!this->edges.defined())
|
|
164
|
+
this->compute_edges_to_triangle_map();
|
|
165
|
+
return this->edges;
|
|
166
|
+
}
|
|
167
|
+
torch::Tensor TriangleMesh::get_edge_to_triangle_offsets()
|
|
168
|
+
{
|
|
169
|
+
if (!this->edge_to_triangle_offsets.defined())
|
|
170
|
+
this->compute_edges_to_triangle_map();
|
|
171
|
+
return this->edge_to_triangle_offsets;
|
|
172
|
+
}
|
|
173
|
+
torch::Tensor TriangleMesh::get_edge_to_triangle_counts()
|
|
174
|
+
{
|
|
175
|
+
if (!this->edge_to_triangle_counts.defined())
|
|
176
|
+
this->compute_edges_to_triangle_map();
|
|
177
|
+
return this->edge_to_triangle_counts;
|
|
178
|
+
}
|
|
179
|
+
torch::Tensor TriangleMesh::get_edge_to_triangle_indices()
|
|
180
|
+
{
|
|
181
|
+
if (!this->edge_to_triangle_indices.defined())
|
|
182
|
+
this->compute_edges_to_triangle_map();
|
|
183
|
+
return this->edge_to_triangle_indices;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
bool TriangleMesh::is_edge_manifold(bool allow_boundary_edge)
|
|
187
|
+
{
|
|
188
|
+
if (this->num_triangles == 0)
|
|
189
|
+
return true;
|
|
190
|
+
torch::Tensor counts = this->get_edge_to_triangle_counts();
|
|
191
|
+
if (allow_boundary_edge)
|
|
192
|
+
{
|
|
193
|
+
return (counts <= 2).all().item<bool>();
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
{
|
|
197
|
+
return (counts == 2).all().item<bool>();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
void TriangleMesh::remove_triangles_by_mask(const torch::Tensor &keep_mask)
|
|
202
|
+
{
|
|
203
|
+
CHECK_INPUT(keep_mask);
|
|
204
|
+
TORCH_CHECK(keep_mask.scalar_type() == torch::kBool, "keep_mask must be a boolean tensor");
|
|
205
|
+
TORCH_CHECK(keep_mask.dim() == 1 && keep_mask.size(0) == this->num_triangles, "keep_mask must have shape (num_triangles,)");
|
|
206
|
+
|
|
207
|
+
this->triangles = this->triangles.index({keep_mask});
|
|
208
|
+
this->num_triangles = this->triangles.size(0);
|
|
209
|
+
|
|
210
|
+
// Invalidate all caches
|
|
211
|
+
this->triangle_areas = torch::Tensor();
|
|
212
|
+
this->triangle_normals = torch::Tensor();
|
|
213
|
+
this->surface_area = torch::Tensor();
|
|
214
|
+
this->bvh.reset();
|
|
215
|
+
this->edges = torch::Tensor();
|
|
216
|
+
this->edge_to_triangle_offsets = torch::Tensor();
|
|
217
|
+
this->edge_to_triangle_counts = torch::Tensor();
|
|
218
|
+
this->edge_to_triangle_indices = torch::Tensor();
|
|
219
|
+
|
|
220
|
+
this->vertex_to_triangle_offsets = torch::Tensor();
|
|
221
|
+
this->vertex_to_triangle_counts = torch::Tensor();
|
|
222
|
+
this->vertex_to_triangle_indices = torch::Tensor();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
int32_t TriangleMesh::get_euler_characteristic()
|
|
226
|
+
{
|
|
227
|
+
int32_t V = this->vertices.size(0);
|
|
228
|
+
int32_t E = this->get_edges().size(0);
|
|
229
|
+
int32_t F = this->num_triangles;
|
|
230
|
+
return V - E + F;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
int32_t TriangleMesh::get_genus()
|
|
234
|
+
{
|
|
235
|
+
// For a single closed connected component, chi = 2 - 2g => g = (2 - chi) / 2
|
|
236
|
+
int32_t chi = this->get_euler_characteristic();
|
|
237
|
+
return (2 - chi) / 2;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
void TriangleMesh::compute_vertices_to_triangle_map()
|
|
241
|
+
{
|
|
242
|
+
if (this->vertex_to_triangle_offsets.defined())
|
|
243
|
+
return;
|
|
244
|
+
|
|
245
|
+
uint32_t num_vertices = this->vertices.size(0);
|
|
246
|
+
triangle_mesh::build_vertices_to_triangle_map(
|
|
247
|
+
num_vertices,
|
|
248
|
+
this->num_triangles,
|
|
249
|
+
this->triangles,
|
|
250
|
+
this->vertex_to_triangle_counts,
|
|
251
|
+
this->vertex_to_triangle_offsets,
|
|
252
|
+
this->vertex_to_triangle_indices);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_vertices_to_triangle_map()
|
|
256
|
+
{
|
|
257
|
+
this->compute_vertices_to_triangle_map();
|
|
258
|
+
return {this->vertex_to_triangle_offsets, this->vertex_to_triangle_counts, this->vertex_to_triangle_indices};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
torch::Tensor TriangleMesh::get_vertex_to_triangle_offsets()
|
|
262
|
+
{
|
|
263
|
+
this->compute_vertices_to_triangle_map();
|
|
264
|
+
return this->vertex_to_triangle_offsets;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
torch::Tensor TriangleMesh::get_vertex_to_triangle_counts()
|
|
268
|
+
{
|
|
269
|
+
this->compute_vertices_to_triangle_map();
|
|
270
|
+
return this->vertex_to_triangle_counts;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
torch::Tensor TriangleMesh::get_vertex_to_triangle_indices() {
|
|
274
|
+
this->compute_vertices_to_triangle_map();
|
|
275
|
+
return this->vertex_to_triangle_indices;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
torch::Tensor TriangleMesh::get_non_manifold_vertices() {
|
|
279
|
+
this->compute_vertices_to_triangle_map();
|
|
280
|
+
return triangle_mesh::get_non_manifold_vertices(
|
|
281
|
+
this->vertices.size(0),
|
|
282
|
+
this->triangles,
|
|
283
|
+
this->vertex_to_triangle_offsets,
|
|
284
|
+
this->vertex_to_triangle_counts,
|
|
285
|
+
this->vertex_to_triangle_indices
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
bool TriangleMesh::is_vertex_manifold() {
|
|
290
|
+
torch::Tensor nm_vertices = this->get_non_manifold_vertices();
|
|
291
|
+
return nm_vertices.size(0) == 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
bool TriangleMesh::is_manifold(bool allow_boundary_edge) {
|
|
295
|
+
return this->is_edge_manifold(allow_boundary_edge) && this->is_vertex_manifold() && !this->is_self_intersection();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void bind_ds_triangle_mesh(py::module_ &m)
|
|
299
|
+
{
|
|
300
|
+
py::class_<TriangleMesh>(m, "TriangleMesh")
|
|
301
|
+
.def(py::init<const torch::Tensor &, const torch::Tensor &, std::optional<torch::Tensor>, std::optional<torch::Tensor>>(),
|
|
302
|
+
py::arg("in_vertices"),
|
|
303
|
+
py::arg("in_triangles"),
|
|
304
|
+
py::arg("in_vertex_normals") = std::nullopt,
|
|
305
|
+
py::arg("in_vertex_colors") = std::nullopt)
|
|
306
|
+
.def_property_readonly("num_triangles", &TriangleMesh::get_num_triangles)
|
|
307
|
+
.def_property_readonly("vertices", &TriangleMesh::get_vertices)
|
|
308
|
+
.def_property_readonly("vertex_normals", &TriangleMesh::get_vertex_normals)
|
|
309
|
+
.def_property_readonly("vertex_colors", &TriangleMesh::get_vertex_colors)
|
|
310
|
+
.def_property_readonly("triangles", &TriangleMesh::get_triangles)
|
|
311
|
+
.def_property_readonly("triangle_areas", &TriangleMesh::get_triangle_areas)
|
|
312
|
+
.def_property_readonly("triangle_normals", &TriangleMesh::get_triangle_normals)
|
|
313
|
+
.def_property_readonly("surface_area", &TriangleMesh::get_surface_area)
|
|
314
|
+
.def_property_readonly("bvh", &TriangleMesh::build_bvh)
|
|
315
|
+
.def("build_bvh", &TriangleMesh::build_bvh)
|
|
316
|
+
.def("get_self_intersection", &TriangleMesh::get_self_intersection)
|
|
317
|
+
.def("is_self_intersection", &TriangleMesh::is_self_intersection)
|
|
318
|
+
.def_property_readonly("edges", &TriangleMesh::get_edges)
|
|
319
|
+
.def_property_readonly("edge_to_triangle_offsets", &TriangleMesh::get_edge_to_triangle_offsets)
|
|
320
|
+
.def_property_readonly("edge_to_triangle_counts", &TriangleMesh::get_edge_to_triangle_counts)
|
|
321
|
+
.def_property_readonly("edge_to_triangle_indices", &TriangleMesh::get_edge_to_triangle_indices)
|
|
322
|
+
.def("compute_triangle_areas", &TriangleMesh::compute_triangle_areas)
|
|
323
|
+
.def("compute_triangle_normals", &TriangleMesh::compute_triangle_normals)
|
|
324
|
+
.def("compute_edges_to_triangle_map", &TriangleMesh::compute_edges_to_triangle_map)
|
|
325
|
+
.def("get_edges_to_triangle_map", &TriangleMesh::get_edges_to_triangle_map)
|
|
326
|
+
.def("compute_vertices_to_triangle_map", &TriangleMesh::compute_vertices_to_triangle_map)
|
|
327
|
+
.def("get_vertices_to_triangle_map", &TriangleMesh::get_vertices_to_triangle_map)
|
|
328
|
+
.def_property_readonly("vertex_to_triangle_offsets", &TriangleMesh::get_vertex_to_triangle_offsets)
|
|
329
|
+
.def_property_readonly("vertex_to_triangle_counts", &TriangleMesh::get_vertex_to_triangle_counts)
|
|
330
|
+
.def_property_readonly("vertex_to_triangle_indices", &TriangleMesh::get_vertex_to_triangle_indices)
|
|
331
|
+
.def("is_edge_manifold", &TriangleMesh::is_edge_manifold, py::arg("allow_boundary_edge") = true)
|
|
332
|
+
.def("is_vertex_manifold", &TriangleMesh::is_vertex_manifold)
|
|
333
|
+
.def("is_manifold", &TriangleMesh::is_manifold, py::arg("allow_boundary_edge") = true)
|
|
334
|
+
.def("get_non_manifold_vertices", &TriangleMesh::get_non_manifold_vertices)
|
|
335
|
+
.def("remove_triangles_by_mask", &TriangleMesh::remove_triangles_by_mask, py::arg("keep_mask"))
|
|
336
|
+
.def_property_readonly("euler_characteristic", &TriangleMesh::get_euler_characteristic)
|
|
337
|
+
.def_property_readonly("genus", &TriangleMesh::get_genus);
|
|
338
|
+
}
|