witwin 0.3.0__py3-none-win_amd64.whl
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.
- witwin/core/__init__.py +53 -0
- witwin/core/geometry/__init__.py +23 -0
- witwin/core/geometry/base.py +211 -0
- witwin/core/geometry/cuda/__init__.py +9 -0
- witwin/core/geometry/cuda/backend.py +177 -0
- witwin/core/geometry/cuda/build.py +288 -0
- witwin/core/geometry/cuda/extension.cpp +29 -0
- witwin/core/geometry/cuda/mesh_sdf_kernels.cu +745 -0
- witwin/core/geometry/cuda/prebuilt/witwin_core_mesh_sdf_cuda.pyd +0 -0
- witwin/core/geometry/mesh.py +426 -0
- witwin/core/geometry/mesh_sdf.py +761 -0
- witwin/core/geometry/polygon.py +91 -0
- witwin/core/geometry/primitives.py +488 -0
- witwin/core/material.py +190 -0
- witwin/core/math.py +113 -0
- witwin-0.3.0.dist-info/METADATA +63 -0
- witwin-0.3.0.dist-info/RECORD +18 -0
- witwin-0.3.0.dist-info/WHEEL +4 -0
witwin/core/__init__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Witwin Core - Shared geometry, material, and structure contracts."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.3.0"
|
|
4
|
+
|
|
5
|
+
from .material import (
|
|
6
|
+
FrequencyMaterialSample,
|
|
7
|
+
Material,
|
|
8
|
+
MaterialCapabilities,
|
|
9
|
+
MaterialSpec,
|
|
10
|
+
StaticMaterialSample,
|
|
11
|
+
Structure,
|
|
12
|
+
)
|
|
13
|
+
from .geometry import (
|
|
14
|
+
Box,
|
|
15
|
+
Cone,
|
|
16
|
+
Cylinder,
|
|
17
|
+
Ellipsoid,
|
|
18
|
+
Geometry,
|
|
19
|
+
GeometryBase,
|
|
20
|
+
GeometrySpec,
|
|
21
|
+
HollowBox,
|
|
22
|
+
Mesh,
|
|
23
|
+
Prism,
|
|
24
|
+
Pyramid,
|
|
25
|
+
Sphere,
|
|
26
|
+
Torus,
|
|
27
|
+
)
|
|
28
|
+
from .math import (
|
|
29
|
+
quat_from_euler,
|
|
30
|
+
quat_identity,
|
|
31
|
+
quat_multiply,
|
|
32
|
+
quat_to_rotation_matrix,
|
|
33
|
+
quat_to_rotation_matrix_np,
|
|
34
|
+
)
|
|
35
|
+
__all__ = [
|
|
36
|
+
"GeometryBase",
|
|
37
|
+
"GeometrySpec",
|
|
38
|
+
"Box", "Sphere", "Cylinder", "Cone", "Ellipsoid",
|
|
39
|
+
"Pyramid", "Prism", "Torus", "HollowBox",
|
|
40
|
+
"Material",
|
|
41
|
+
"MaterialCapabilities",
|
|
42
|
+
"MaterialSpec",
|
|
43
|
+
"Mesh",
|
|
44
|
+
"FrequencyMaterialSample",
|
|
45
|
+
"Geometry",
|
|
46
|
+
"StaticMaterialSample",
|
|
47
|
+
"Structure",
|
|
48
|
+
"quat_from_euler",
|
|
49
|
+
"quat_identity",
|
|
50
|
+
"quat_multiply",
|
|
51
|
+
"quat_to_rotation_matrix",
|
|
52
|
+
"quat_to_rotation_matrix_np",
|
|
53
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Shared differentiable geometry package."""
|
|
2
|
+
|
|
3
|
+
from .base import GeometryBase, GeometrySpec
|
|
4
|
+
from .mesh import Mesh
|
|
5
|
+
from .primitives import Box, Cone, Cylinder, Ellipsoid, HollowBox, Prism, Pyramid, Sphere, Torus
|
|
6
|
+
|
|
7
|
+
Geometry = Box | Sphere | Cylinder | Cone | Ellipsoid | Pyramid | Prism | Torus | HollowBox | Mesh
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"GeometryBase",
|
|
11
|
+
"GeometrySpec",
|
|
12
|
+
"Box",
|
|
13
|
+
"Sphere",
|
|
14
|
+
"Cylinder",
|
|
15
|
+
"Cone",
|
|
16
|
+
"Ellipsoid",
|
|
17
|
+
"Pyramid",
|
|
18
|
+
"Prism",
|
|
19
|
+
"Torus",
|
|
20
|
+
"HollowBox",
|
|
21
|
+
"Mesh",
|
|
22
|
+
"Geometry",
|
|
23
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Shared geometry base classes and tensor helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
from ..math import (
|
|
11
|
+
quat_from_euler,
|
|
12
|
+
quat_identity,
|
|
13
|
+
quat_to_rotation_matrix,
|
|
14
|
+
quat_to_rotation_matrix_np,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _as_position(value, *, device=None) -> torch.Tensor:
|
|
19
|
+
if isinstance(value, torch.Tensor):
|
|
20
|
+
return value.to(device=device, dtype=torch.float32)
|
|
21
|
+
return torch.tensor([float(v) for v in value], dtype=torch.float32, device=device)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _as_rotation(value, *, device=None) -> torch.Tensor:
|
|
25
|
+
"""Accept quaternion (4,), Euler tuple (3,), or None -> identity."""
|
|
26
|
+
if value is None:
|
|
27
|
+
return quat_identity(device=device)
|
|
28
|
+
if isinstance(value, torch.Tensor):
|
|
29
|
+
if value.shape == (4,):
|
|
30
|
+
return value.to(device=device, dtype=torch.float32)
|
|
31
|
+
if value.shape == (3,):
|
|
32
|
+
return quat_from_euler(value[0], value[1], value[2], device=device)
|
|
33
|
+
raise ValueError(f"rotation tensor must be shape (4,) quaternion or (3,) Euler, got {value.shape}")
|
|
34
|
+
seq = list(value)
|
|
35
|
+
if len(seq) == 4:
|
|
36
|
+
return torch.tensor([float(v) for v in seq], dtype=torch.float32, device=device)
|
|
37
|
+
if len(seq) == 3:
|
|
38
|
+
return quat_from_euler(float(seq[0]), float(seq[1]), float(seq[2]), device=device)
|
|
39
|
+
raise ValueError(f"rotation must have 3 (Euler) or 4 (quaternion) elements, got {len(seq)}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _as_scalar(value, *, device=None) -> torch.Tensor:
|
|
43
|
+
if isinstance(value, torch.Tensor):
|
|
44
|
+
return value.to(device=device, dtype=torch.float32).reshape(())
|
|
45
|
+
return torch.tensor(float(value), dtype=torch.float32, device=device)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _as_vec3(value, *, device=None) -> torch.Tensor:
|
|
49
|
+
if isinstance(value, torch.Tensor):
|
|
50
|
+
return value.to(device=device, dtype=torch.float32)
|
|
51
|
+
if isinstance(value, (int, float)):
|
|
52
|
+
scalar = float(value)
|
|
53
|
+
return torch.tensor([scalar, scalar, scalar], dtype=torch.float32, device=device)
|
|
54
|
+
return torch.tensor([float(v) for v in value], dtype=torch.float32, device=device)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _rotate_coords(xx, yy, zz, position: torch.Tensor, rotation: torch.Tensor):
|
|
58
|
+
"""Translate + inverse-rotate 3D grid coordinates into local frame."""
|
|
59
|
+
dx = xx - position[0]
|
|
60
|
+
dy = yy - position[1]
|
|
61
|
+
dz = zz - position[2]
|
|
62
|
+
rotation_matrix = quat_to_rotation_matrix(rotation)
|
|
63
|
+
rotation_inverse = rotation_matrix.T
|
|
64
|
+
dx_rot = rotation_inverse[0, 0] * dx + rotation_inverse[0, 1] * dy + rotation_inverse[0, 2] * dz
|
|
65
|
+
dy_rot = rotation_inverse[1, 0] * dx + rotation_inverse[1, 1] * dy + rotation_inverse[1, 2] * dz
|
|
66
|
+
dz_rot = rotation_inverse[2, 0] * dx + rotation_inverse[2, 1] * dy + rotation_inverse[2, 2] * dz
|
|
67
|
+
return dx_rot, dy_rot, dz_rot
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _axial_split(dx, dy, dz, axis: str):
|
|
71
|
+
"""Return (axial, radial_a, radial_b) for a named axis."""
|
|
72
|
+
if axis == "z":
|
|
73
|
+
return dz, dx, dy
|
|
74
|
+
if axis == "y":
|
|
75
|
+
return dy, dx, dz
|
|
76
|
+
return dx, dy, dz
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _apply_rotation_np(vertices: np.ndarray, rotation_matrix: np.ndarray) -> np.ndarray:
|
|
80
|
+
"""Apply a (3,3) rotation matrix to (N,3) vertices."""
|
|
81
|
+
return (vertices @ rotation_matrix.T).astype(np.float32)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _constant_tensor(data, *, device, dtype=torch.float32) -> torch.Tensor:
|
|
85
|
+
return torch.tensor(data, dtype=dtype, device=device)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _remap_axis_torch(vertices: torch.Tensor, axis: str) -> torch.Tensor:
|
|
89
|
+
"""Remap local z-up coordinates to the requested axis."""
|
|
90
|
+
if axis == "y":
|
|
91
|
+
return vertices[:, [0, 2, 1]]
|
|
92
|
+
if axis == "x":
|
|
93
|
+
return vertices[:, [2, 1, 0]]
|
|
94
|
+
return vertices
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _coordinate_spacing(coord: torch.Tensor, dim: int) -> torch.Tensor | None:
|
|
98
|
+
if not isinstance(coord, torch.Tensor) or coord.ndim <= dim or coord.shape[dim] <= 1:
|
|
99
|
+
return None
|
|
100
|
+
delta = torch.diff(coord, dim=dim).abs()
|
|
101
|
+
positive = delta[delta > 0]
|
|
102
|
+
if positive.numel() == 0:
|
|
103
|
+
return None
|
|
104
|
+
return positive.min()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _default_beta_from_coords(xx, yy, zz, *, reference: torch.Tensor) -> torch.Tensor:
|
|
108
|
+
spacings = [
|
|
109
|
+
spacing
|
|
110
|
+
for spacing in (
|
|
111
|
+
_coordinate_spacing(xx, 0),
|
|
112
|
+
_coordinate_spacing(yy, 1),
|
|
113
|
+
_coordinate_spacing(zz, 2),
|
|
114
|
+
)
|
|
115
|
+
if spacing is not None
|
|
116
|
+
]
|
|
117
|
+
if not spacings:
|
|
118
|
+
return reference.new_tensor(1.0e-3)
|
|
119
|
+
|
|
120
|
+
spacing = spacings[0].to(device=reference.device, dtype=reference.dtype)
|
|
121
|
+
for candidate in spacings[1:]:
|
|
122
|
+
spacing = torch.minimum(spacing, candidate.to(device=reference.device, dtype=reference.dtype))
|
|
123
|
+
return spacing * 0.05
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _resolve_beta(beta, xx, yy, zz, *, reference: torch.Tensor) -> torch.Tensor:
|
|
127
|
+
if beta is None:
|
|
128
|
+
resolved = _default_beta_from_coords(xx, yy, zz, reference=reference)
|
|
129
|
+
else:
|
|
130
|
+
resolved = torch.as_tensor(beta, device=reference.device, dtype=reference.dtype)
|
|
131
|
+
return torch.clamp(resolved, min=torch.finfo(reference.dtype).eps)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def occupancy_from_signed_distance(signed_distance: torch.Tensor, *, xx, yy, zz, offset=0.0, beta=None) -> torch.Tensor:
|
|
135
|
+
offset_tensor = torch.as_tensor(offset, device=signed_distance.device, dtype=signed_distance.dtype)
|
|
136
|
+
beta_tensor = _resolve_beta(beta, xx, yy, zz, reference=signed_distance)
|
|
137
|
+
occupancy = 0.5 * (1.0 - torch.tanh((signed_distance - offset_tensor) / beta_tensor))
|
|
138
|
+
return occupancy.clamp(0.0, 1.0)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@runtime_checkable
|
|
142
|
+
class GeometrySpec(Protocol):
|
|
143
|
+
"""Minimal geometry contract consumed by solver-specific scenes."""
|
|
144
|
+
|
|
145
|
+
kind: str
|
|
146
|
+
|
|
147
|
+
def to_mesh(self, segments: int = 16) -> tuple:
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class GeometryBase:
|
|
152
|
+
"""Base class for all geometry primitives."""
|
|
153
|
+
|
|
154
|
+
kind: str = "base"
|
|
155
|
+
|
|
156
|
+
def __init__(self, position=(0, 0, 0), rotation=None, *, device=None):
|
|
157
|
+
self.position: torch.Tensor = _as_position(position, device=device)
|
|
158
|
+
self.rotation: torch.Tensor = _as_rotation(rotation, device=device)
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def device(self):
|
|
162
|
+
return self.position.device
|
|
163
|
+
|
|
164
|
+
def _local_coords(self, xx, yy, zz):
|
|
165
|
+
return _rotate_coords(xx, yy, zz, self.position, self.rotation)
|
|
166
|
+
|
|
167
|
+
def _rotation_matrix_np(self) -> np.ndarray:
|
|
168
|
+
return quat_to_rotation_matrix_np(self.rotation)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _validate_axis(axis: str) -> str:
|
|
172
|
+
axis_name = str(axis).lower()
|
|
173
|
+
if axis_name not in {"x", "y", "z"}:
|
|
174
|
+
raise ValueError("axis must be 'x', 'y', or 'z'.")
|
|
175
|
+
return axis_name
|
|
176
|
+
|
|
177
|
+
def _apply_axis_transform(self, vertices: torch.Tensor, axis: str) -> torch.Tensor:
|
|
178
|
+
return _remap_axis_torch(vertices, axis)
|
|
179
|
+
|
|
180
|
+
def _transform_mesh_verts(self, vertices: torch.Tensor) -> torch.Tensor:
|
|
181
|
+
if not isinstance(vertices, torch.Tensor):
|
|
182
|
+
vertices = torch.as_tensor(vertices, dtype=torch.float32, device=self.device)
|
|
183
|
+
rotation_matrix = quat_to_rotation_matrix(self.rotation.to(device=vertices.device, dtype=vertices.dtype))
|
|
184
|
+
position = self.position.to(device=vertices.device, dtype=vertices.dtype)
|
|
185
|
+
return vertices @ rotation_matrix.T + position
|
|
186
|
+
|
|
187
|
+
def with_material(self, material, **kwargs):
|
|
188
|
+
from ..material import Structure
|
|
189
|
+
|
|
190
|
+
return Structure(geometry=self, material=material, **kwargs)
|
|
191
|
+
|
|
192
|
+
def signed_distance(self, xx, yy, zz):
|
|
193
|
+
raise NotImplementedError
|
|
194
|
+
|
|
195
|
+
def to_mask(self, xx, yy, zz, offset=0.0, beta=None):
|
|
196
|
+
signed_distance = self.signed_distance(xx, yy, zz)
|
|
197
|
+
return occupancy_from_signed_distance(
|
|
198
|
+
signed_distance,
|
|
199
|
+
xx=xx,
|
|
200
|
+
yy=yy,
|
|
201
|
+
zz=zz,
|
|
202
|
+
offset=offset,
|
|
203
|
+
beta=beta,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def to_mesh(self, segments=16):
|
|
207
|
+
raise NotImplementedError
|
|
208
|
+
|
|
209
|
+
def __repr__(self):
|
|
210
|
+
fields = ", ".join(f"{key}={value}" for key, value in self.__dict__.items() if not key.startswith("_"))
|
|
211
|
+
return f"{type(self).__name__}({fields})"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Loader for the native CUDA mesh-SDF extension.
|
|
2
|
+
|
|
3
|
+
Exposes a module object that is call-compatible with the former JIT-kernel
|
|
4
|
+
module: ``module.<kernelName>(**kwargs).launchRaw(blockSize=..., gridSize=...)``.
|
|
5
|
+
The kernels compute their own launch grid, so ``launchRaw`` ignores the block /
|
|
6
|
+
grid hints and simply dispatches the compiled function.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_COMPILED_EXTENSION: Any | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_available() -> bool:
|
|
21
|
+
return bool(torch.cuda.is_available())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_compiled_extension(*, verbose: bool = False) -> Any:
|
|
25
|
+
global _COMPILED_EXTENSION
|
|
26
|
+
if _COMPILED_EXTENSION is None:
|
|
27
|
+
from .build import build_extension
|
|
28
|
+
|
|
29
|
+
_COMPILED_EXTENSION = build_extension(verbose=verbose)
|
|
30
|
+
return _COMPILED_EXTENSION
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _query_mesh_unsigned_distance(*, triangles, points, unsignedDistance, closestTriangleIndex):
|
|
34
|
+
get_compiled_extension().query_mesh_unsigned_distance(
|
|
35
|
+
triangles, points, unsignedDistance, closestTriangleIndex
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _query_mesh_distance_and_winding(*, triangles, points, unsignedDistance, windingAngle, closestTriangleIndex):
|
|
40
|
+
get_compiled_extension().query_mesh_distance_and_winding(
|
|
41
|
+
triangles, points, unsignedDistance, windingAngle, closestTriangleIndex
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _query_mesh_unsigned_distance_bvh(
|
|
46
|
+
*,
|
|
47
|
+
triangles,
|
|
48
|
+
points,
|
|
49
|
+
nodeBBoxMin,
|
|
50
|
+
nodeBBoxMax,
|
|
51
|
+
nodeLeft,
|
|
52
|
+
nodeRight,
|
|
53
|
+
nodeStart,
|
|
54
|
+
nodeCount,
|
|
55
|
+
triangleIndices,
|
|
56
|
+
unsignedDistance,
|
|
57
|
+
closestTriangleIndex,
|
|
58
|
+
):
|
|
59
|
+
get_compiled_extension().query_mesh_unsigned_distance_bvh(
|
|
60
|
+
triangles,
|
|
61
|
+
points,
|
|
62
|
+
nodeBBoxMin,
|
|
63
|
+
nodeBBoxMax,
|
|
64
|
+
nodeLeft,
|
|
65
|
+
nodeRight,
|
|
66
|
+
nodeStart,
|
|
67
|
+
nodeCount,
|
|
68
|
+
triangleIndices,
|
|
69
|
+
unsignedDistance,
|
|
70
|
+
closestTriangleIndex,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _query_mesh_parity_sign_bvh(
|
|
75
|
+
*,
|
|
76
|
+
triangles,
|
|
77
|
+
points,
|
|
78
|
+
nodeBBoxMin,
|
|
79
|
+
nodeBBoxMax,
|
|
80
|
+
nodeLeft,
|
|
81
|
+
nodeRight,
|
|
82
|
+
nodeStart,
|
|
83
|
+
nodeCount,
|
|
84
|
+
triangleIndices,
|
|
85
|
+
jitterScale,
|
|
86
|
+
inside,
|
|
87
|
+
):
|
|
88
|
+
get_compiled_extension().query_mesh_parity_sign_bvh(
|
|
89
|
+
triangles,
|
|
90
|
+
points,
|
|
91
|
+
nodeBBoxMin,
|
|
92
|
+
nodeBBoxMax,
|
|
93
|
+
nodeLeft,
|
|
94
|
+
nodeRight,
|
|
95
|
+
nodeStart,
|
|
96
|
+
nodeCount,
|
|
97
|
+
triangleIndices,
|
|
98
|
+
float(jitterScale),
|
|
99
|
+
inside,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _backward_mesh_unsigned_distance(
|
|
104
|
+
*, triangles, points, closestTriangleIndex, gradUnsignedDistance, gradTriangles, gradPoints
|
|
105
|
+
):
|
|
106
|
+
get_compiled_extension().backward_mesh_unsigned_distance(
|
|
107
|
+
triangles, points, closestTriangleIndex, gradUnsignedDistance, gradTriangles, gradPoints
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _backward_mesh_distance_and_winding(
|
|
112
|
+
*,
|
|
113
|
+
triangles,
|
|
114
|
+
points,
|
|
115
|
+
closestTriangleIndex,
|
|
116
|
+
gradUnsignedDistance,
|
|
117
|
+
gradWindingAngle,
|
|
118
|
+
gradTriangles,
|
|
119
|
+
gradPoints,
|
|
120
|
+
):
|
|
121
|
+
get_compiled_extension().backward_mesh_distance_and_winding(
|
|
122
|
+
triangles,
|
|
123
|
+
points,
|
|
124
|
+
closestTriangleIndex,
|
|
125
|
+
gradUnsignedDistance,
|
|
126
|
+
gradWindingAngle,
|
|
127
|
+
gradTriangles,
|
|
128
|
+
gradPoints,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_KERNELS: dict[str, Callable[..., None]] = {
|
|
133
|
+
"queryMeshUnsignedDistance": _query_mesh_unsigned_distance,
|
|
134
|
+
"queryMeshDistanceAndWinding": _query_mesh_distance_and_winding,
|
|
135
|
+
"queryMeshUnsignedDistanceBVH": _query_mesh_unsigned_distance_bvh,
|
|
136
|
+
"queryMeshParitySignBVH": _query_mesh_parity_sign_bvh,
|
|
137
|
+
"backwardMeshUnsignedDistance": _backward_mesh_unsigned_distance,
|
|
138
|
+
"backwardMeshDistanceAndWinding": _backward_mesh_distance_and_winding,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class _Launch:
|
|
143
|
+
__slots__ = ("_kernel", "_kwargs")
|
|
144
|
+
|
|
145
|
+
def __init__(self, kernel: Callable[..., None], kwargs: dict):
|
|
146
|
+
self._kernel = kernel
|
|
147
|
+
self._kwargs = kwargs
|
|
148
|
+
|
|
149
|
+
def launchRaw(self, *, blockSize=None, gridSize=None): # noqa: N802 - kernel launch entry point
|
|
150
|
+
del blockSize, gridSize
|
|
151
|
+
self._kernel(**self._kwargs)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class NativeMeshSDFModule:
|
|
156
|
+
def __getattr__(self, name: str):
|
|
157
|
+
kernel = _KERNELS.get(name)
|
|
158
|
+
if kernel is None:
|
|
159
|
+
raise AttributeError(f"Native CUDA mesh SDF backend does not implement kernel {name!r}.")
|
|
160
|
+
|
|
161
|
+
def bind(**kwargs):
|
|
162
|
+
return _Launch(kernel, kwargs)
|
|
163
|
+
|
|
164
|
+
bind.__name__ = name
|
|
165
|
+
object.__setattr__(self, name, bind)
|
|
166
|
+
return bind
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
_NATIVE_MODULE = NativeMeshSDFModule()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_native_mesh_sdf_module(*, verbose: bool = False) -> NativeMeshSDFModule:
|
|
173
|
+
"""Compile the extension (once) and return the launch-compatible module."""
|
|
174
|
+
if not is_available():
|
|
175
|
+
raise RuntimeError("Native CUDA mesh SDF backend requires torch.cuda.is_available() to be True.")
|
|
176
|
+
get_compiled_extension(verbose=verbose)
|
|
177
|
+
return _NATIVE_MODULE
|