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
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""JIT build of the native CUDA mesh-SDF extension.
|
|
2
|
+
|
|
3
|
+
Self-contained ``torch.utils.cpp_extension.load`` wrapper with the Windows
|
|
4
|
+
MSVC + CUDA toolchain discovery the platform needs. ``witwin.core`` must not
|
|
5
|
+
depend on ``witwin.maxwell``, so the toolchain helpers are duplicated here
|
|
6
|
+
rather than imported from the maxwell FDTD CUDA build.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import torch
|
|
19
|
+
from torch.utils.cpp_extension import load
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
EXTENSION_NAME = "witwin_core_mesh_sdf_cuda"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _candidate_vcvars64_paths() -> list[Path]:
|
|
26
|
+
paths: list[Path] = []
|
|
27
|
+
program_files_x86 = os.environ.get("ProgramFiles(x86)")
|
|
28
|
+
if program_files_x86:
|
|
29
|
+
vswhere = Path(program_files_x86) / "Microsoft Visual Studio" / "Installer" / "vswhere.exe"
|
|
30
|
+
if vswhere.exists():
|
|
31
|
+
try:
|
|
32
|
+
install_root = subprocess.check_output(
|
|
33
|
+
[
|
|
34
|
+
str(vswhere),
|
|
35
|
+
"-latest",
|
|
36
|
+
"-products",
|
|
37
|
+
"*",
|
|
38
|
+
"-requires",
|
|
39
|
+
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
|
40
|
+
"-property",
|
|
41
|
+
"installationPath",
|
|
42
|
+
],
|
|
43
|
+
text=True,
|
|
44
|
+
encoding="mbcs",
|
|
45
|
+
errors="replace",
|
|
46
|
+
).strip()
|
|
47
|
+
except (OSError, subprocess.CalledProcessError):
|
|
48
|
+
install_root = ""
|
|
49
|
+
if install_root:
|
|
50
|
+
paths.append(Path(install_root) / "VC" / "Auxiliary" / "Build" / "vcvars64.bat")
|
|
51
|
+
|
|
52
|
+
root = os.environ.get("VSINSTALLDIR")
|
|
53
|
+
if root:
|
|
54
|
+
paths.append(Path(root) / "VC" / "Auxiliary" / "Build" / "vcvars64.bat")
|
|
55
|
+
|
|
56
|
+
program_files = os.environ.get("ProgramFiles", r"C:\Program Files")
|
|
57
|
+
for edition in ("Community", "Professional", "Enterprise", "BuildTools"):
|
|
58
|
+
paths.append(
|
|
59
|
+
Path(program_files)
|
|
60
|
+
/ "Microsoft Visual Studio"
|
|
61
|
+
/ "2022"
|
|
62
|
+
/ edition
|
|
63
|
+
/ "VC"
|
|
64
|
+
/ "Auxiliary"
|
|
65
|
+
/ "Build"
|
|
66
|
+
/ "vcvars64.bat"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
seen: set[Path] = set()
|
|
70
|
+
unique_paths: list[Path] = []
|
|
71
|
+
for path in paths:
|
|
72
|
+
resolved = path.resolve() if path.exists() else path
|
|
73
|
+
if resolved in seen:
|
|
74
|
+
continue
|
|
75
|
+
seen.add(resolved)
|
|
76
|
+
unique_paths.append(path)
|
|
77
|
+
return unique_paths
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_vcvars64_environment() -> bool:
|
|
81
|
+
for vcvars in _candidate_vcvars64_paths():
|
|
82
|
+
if not vcvars.exists():
|
|
83
|
+
continue
|
|
84
|
+
fd, probe_name = tempfile.mkstemp(prefix="witwin_core_mesh_sdf_vcvars_probe_", suffix=".cmd")
|
|
85
|
+
os.close(fd)
|
|
86
|
+
probe = Path(probe_name)
|
|
87
|
+
probe.write_text(f'@echo off\ncall "{vcvars}" >nul\nset\n', encoding="utf-8")
|
|
88
|
+
try:
|
|
89
|
+
output = subprocess.check_output(
|
|
90
|
+
["cmd.exe", "/d", "/c", str(probe)],
|
|
91
|
+
text=True,
|
|
92
|
+
encoding="mbcs",
|
|
93
|
+
errors="replace",
|
|
94
|
+
)
|
|
95
|
+
except (OSError, subprocess.CalledProcessError):
|
|
96
|
+
continue
|
|
97
|
+
finally:
|
|
98
|
+
try:
|
|
99
|
+
probe.unlink()
|
|
100
|
+
except OSError:
|
|
101
|
+
pass
|
|
102
|
+
updates: dict[str, str] = {}
|
|
103
|
+
for line in output.splitlines():
|
|
104
|
+
if "=" not in line:
|
|
105
|
+
continue
|
|
106
|
+
key, value = line.split("=", 1)
|
|
107
|
+
updates[key] = value
|
|
108
|
+
for key, value in updates.items():
|
|
109
|
+
os.environ[key] = value
|
|
110
|
+
path_value = updates.get("PATH", updates.get("Path"))
|
|
111
|
+
if path_value is not None:
|
|
112
|
+
os.environ["PATH"] = path_value
|
|
113
|
+
os.environ["Path"] = path_value
|
|
114
|
+
return True
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _ensure_windows_build_tools_on_path() -> None:
|
|
119
|
+
if os.name != "nt":
|
|
120
|
+
return
|
|
121
|
+
if shutil.which("cl") is None:
|
|
122
|
+
_load_vcvars64_environment()
|
|
123
|
+
if shutil.which("cl") is None:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
current_path = os.environ.get("PATH") or os.environ.get("Path") or ""
|
|
127
|
+
prefixes: list[str] = []
|
|
128
|
+
vc_tools = os.environ.get("VCToolsInstallDir")
|
|
129
|
+
if vc_tools:
|
|
130
|
+
prefixes.append(str(Path(vc_tools) / "bin" / "Hostx64" / "x64"))
|
|
131
|
+
vs_install = os.environ.get("VSINSTALLDIR")
|
|
132
|
+
if vs_install:
|
|
133
|
+
prefixes.append(
|
|
134
|
+
str(Path(vs_install) / "Common7" / "IDE" / "CommonExtensions" / "Microsoft" / "CMake" / "Ninja")
|
|
135
|
+
)
|
|
136
|
+
if not prefixes:
|
|
137
|
+
return
|
|
138
|
+
merged_path = os.pathsep.join([*prefixes, current_path])
|
|
139
|
+
os.environ["PATH"] = merged_path
|
|
140
|
+
os.environ["Path"] = merged_path
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _ensure_cuda_home_from_nvcc() -> None:
|
|
144
|
+
if os.environ.get("CUDA_HOME") or os.environ.get("CUDA_PATH"):
|
|
145
|
+
return
|
|
146
|
+
nvcc = shutil.which("nvcc")
|
|
147
|
+
if nvcc is None:
|
|
148
|
+
return
|
|
149
|
+
cuda_home = Path(nvcc).resolve().parents[1]
|
|
150
|
+
os.environ["CUDA_HOME"] = str(cuda_home)
|
|
151
|
+
if os.name == "nt":
|
|
152
|
+
os.environ["CUDA_PATH"] = str(cuda_home)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _conda_torch_ldflags() -> list[str]:
|
|
156
|
+
# Conda-distributed torch keeps c10.lib/torch_cuda.lib in <env>\Library\lib
|
|
157
|
+
# instead of site-packages\torch\lib, which cpp_extension does not search.
|
|
158
|
+
if os.name != "nt":
|
|
159
|
+
return []
|
|
160
|
+
library_lib = Path(sys.prefix) / "Library" / "lib"
|
|
161
|
+
if (library_lib / "c10.lib").exists():
|
|
162
|
+
return [f"/LIBPATH:{library_lib}"]
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def source_root() -> Path:
|
|
167
|
+
return Path(__file__).resolve().parent
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def prebuilt_root() -> Path:
|
|
171
|
+
return source_root() / "prebuilt"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def extension_suffix() -> str:
|
|
175
|
+
return ".pyd" if os.name == "nt" else ".so"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def prebuilt_extension_path() -> Path:
|
|
179
|
+
return prebuilt_root() / f"{EXTENSION_NAME}{extension_suffix()}"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def extension_sources() -> list[Path]:
|
|
183
|
+
root = source_root()
|
|
184
|
+
return [root / "extension.cpp", root / "mesh_sdf_kernels.cu"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _cuda_gencode_flags() -> list[str]:
|
|
188
|
+
"""Translate the release architecture list directly into nvcc flags."""
|
|
189
|
+
value = os.environ.get("WITWIN_CUDA_GENCODE_ARCHES")
|
|
190
|
+
if not value:
|
|
191
|
+
return []
|
|
192
|
+
flags: list[str] = []
|
|
193
|
+
for entry in value.split(";"):
|
|
194
|
+
entry = entry.strip()
|
|
195
|
+
if not entry:
|
|
196
|
+
continue
|
|
197
|
+
include_ptx = entry.endswith("+PTX")
|
|
198
|
+
number = entry.removesuffix("+PTX").replace(".", "")
|
|
199
|
+
if not number.isdigit():
|
|
200
|
+
raise ValueError(f"Invalid CUDA architecture {entry!r} in WITWIN_CUDA_GENCODE_ARCHES.")
|
|
201
|
+
flags.append(f"-gencode=arch=compute_{number},code=sm_{number}")
|
|
202
|
+
if include_ptx:
|
|
203
|
+
flags.append(f"-gencode=arch=compute_{number},code=compute_{number}")
|
|
204
|
+
return flags
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class _StableOpsModule:
|
|
208
|
+
"""Attribute-compatible view of the Stable ABI dispatcher operators."""
|
|
209
|
+
|
|
210
|
+
def __init__(self, library_path: Path) -> None:
|
|
211
|
+
self.__file__ = str(library_path)
|
|
212
|
+
|
|
213
|
+
def is_available(self) -> bool:
|
|
214
|
+
return bool(torch.cuda.is_available())
|
|
215
|
+
|
|
216
|
+
def __getattr__(self, name: str):
|
|
217
|
+
return getattr(torch.ops.witwin_core_mesh_sdf_cuda, name)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _load_extension_file(library_path: Path) -> _StableOpsModule:
|
|
221
|
+
torch.ops.load_library(str(library_path))
|
|
222
|
+
if not hasattr(torch.ops.witwin_core_mesh_sdf_cuda, "query_mesh_unsigned_distance"):
|
|
223
|
+
raise ImportError(f"{library_path} does not register the Stable ABI mesh SDF operators.")
|
|
224
|
+
return _StableOpsModule(library_path)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _load_packaged_prebuilt_extension():
|
|
228
|
+
# Wheels ship a Stable ABI library in ``prebuilt/`` so installs never invoke
|
|
229
|
+
# nvcc/MSVC and the same binary loads across supported Python/Torch versions.
|
|
230
|
+
module_path = prebuilt_extension_path()
|
|
231
|
+
if not module_path.exists():
|
|
232
|
+
return None
|
|
233
|
+
return _load_extension_file(module_path)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _load_prebuilt_extension(build_directory: Path):
|
|
237
|
+
# Load the already-built module directly, bypassing the build toolchain.
|
|
238
|
+
module_path = build_directory / f"{EXTENSION_NAME}{extension_suffix()}"
|
|
239
|
+
if not module_path.exists():
|
|
240
|
+
raise FileNotFoundError(
|
|
241
|
+
f"WITWIN_CORE_MESH_SDF_CUDA_PREBUILT=1 but {module_path} does not exist; "
|
|
242
|
+
"run a normal build first."
|
|
243
|
+
)
|
|
244
|
+
return _load_extension_file(module_path)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _jit_build(build_directory: Path, *, verbose: bool):
|
|
248
|
+
_ensure_windows_build_tools_on_path()
|
|
249
|
+
_ensure_cuda_home_from_nvcc()
|
|
250
|
+
build_directory.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
library_path = load(
|
|
252
|
+
name=EXTENSION_NAME,
|
|
253
|
+
sources=[str(path) for path in extension_sources()],
|
|
254
|
+
build_directory=str(build_directory),
|
|
255
|
+
extra_cflags=(
|
|
256
|
+
["/O2", "/DTORCH_TARGET_VERSION=0x020a000000000000"]
|
|
257
|
+
if os.name == "nt"
|
|
258
|
+
else ["-O3", "-DTORCH_TARGET_VERSION=0x020a000000000000"]
|
|
259
|
+
),
|
|
260
|
+
extra_cuda_cflags=[
|
|
261
|
+
"-O3",
|
|
262
|
+
"-DTORCH_TARGET_VERSION=0x020a000000000000",
|
|
263
|
+
"-DUSE_CUDA",
|
|
264
|
+
*_cuda_gencode_flags(),
|
|
265
|
+
],
|
|
266
|
+
extra_ldflags=_conda_torch_ldflags(),
|
|
267
|
+
is_python_module=False,
|
|
268
|
+
verbose=verbose,
|
|
269
|
+
)
|
|
270
|
+
return _StableOpsModule(Path(library_path))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def build_extension(*, verbose: bool = False):
|
|
274
|
+
default_build_directory = Path(tempfile.gettempdir()) / EXTENSION_NAME / "stable_abi_v1"
|
|
275
|
+
build_directory = Path(os.environ.get("WITWIN_CORE_MESH_SDF_CUDA_BUILD_DIR", default_build_directory))
|
|
276
|
+
|
|
277
|
+
if os.environ.get("WITWIN_CORE_MESH_SDF_CUDA_SKIP_PREBUILT") != "1":
|
|
278
|
+
try:
|
|
279
|
+
module = _load_packaged_prebuilt_extension()
|
|
280
|
+
except Exception: # noqa: BLE001 - stale/ABI-mismatched prebuilt, rebuild instead
|
|
281
|
+
module = None
|
|
282
|
+
if module is not None:
|
|
283
|
+
return module
|
|
284
|
+
|
|
285
|
+
if os.environ.get("WITWIN_CORE_MESH_SDF_CUDA_PREBUILT") == "1":
|
|
286
|
+
return _load_prebuilt_extension(build_directory)
|
|
287
|
+
|
|
288
|
+
return _jit_build(build_directory, verbose=verbose)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#include <torch/csrc/stable/library.h>
|
|
2
|
+
|
|
3
|
+
STABLE_TORCH_LIBRARY(witwin_core_mesh_sdf_cuda, m) {
|
|
4
|
+
m.def(
|
|
5
|
+
"query_mesh_unsigned_distance(Tensor triangles, Tensor points, "
|
|
6
|
+
"Tensor(a!) unsigned_distance, Tensor(b!) closest_triangle_index) -> ()");
|
|
7
|
+
m.def(
|
|
8
|
+
"query_mesh_distance_and_winding(Tensor triangles, Tensor points, "
|
|
9
|
+
"Tensor(a!) unsigned_distance, Tensor(b!) winding_angle, "
|
|
10
|
+
"Tensor(c!) closest_triangle_index) -> ()");
|
|
11
|
+
m.def(
|
|
12
|
+
"query_mesh_unsigned_distance_bvh(Tensor triangles, Tensor points, "
|
|
13
|
+
"Tensor node_bbox_min, Tensor node_bbox_max, Tensor node_left, Tensor node_right, "
|
|
14
|
+
"Tensor node_start, Tensor node_count, Tensor triangle_indices, "
|
|
15
|
+
"Tensor(a!) unsigned_distance, Tensor(b!) closest_triangle_index) -> ()");
|
|
16
|
+
m.def(
|
|
17
|
+
"query_mesh_parity_sign_bvh(Tensor triangles, Tensor points, "
|
|
18
|
+
"Tensor node_bbox_min, Tensor node_bbox_max, Tensor node_left, Tensor node_right, "
|
|
19
|
+
"Tensor node_start, Tensor node_count, Tensor triangle_indices, float jitter_scale, "
|
|
20
|
+
"Tensor(a!) inside) -> ()");
|
|
21
|
+
m.def(
|
|
22
|
+
"backward_mesh_unsigned_distance(Tensor triangles, Tensor points, "
|
|
23
|
+
"Tensor closest_triangle_index, Tensor grad_unsigned_distance, "
|
|
24
|
+
"Tensor(a!) grad_triangles, Tensor(b!) grad_points) -> ()");
|
|
25
|
+
m.def(
|
|
26
|
+
"backward_mesh_distance_and_winding(Tensor triangles, Tensor points, "
|
|
27
|
+
"Tensor closest_triangle_index, Tensor grad_unsigned_distance, Tensor grad_winding_angle, "
|
|
28
|
+
"Tensor(a!) grad_triangles, Tensor(b!) grad_points) -> ()");
|
|
29
|
+
}
|