warp-lang 1.1.0__py3-none-manylinux2014_x86_64.whl → 1.2.1__py3-none-manylinux2014_x86_64.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.
Potentially problematic release.
This version of warp-lang might be problematic. Click here for more details.
- warp/bin/warp-clang.so +0 -0
- warp/bin/warp.so +0 -0
- warp/build.py +10 -37
- warp/build_dll.py +2 -2
- warp/builtins.py +274 -6
- warp/codegen.py +51 -4
- warp/config.py +2 -2
- warp/constants.py +4 -0
- warp/context.py +422 -203
- warp/examples/benchmarks/benchmark_api.py +0 -2
- warp/examples/benchmarks/benchmark_cloth_warp.py +0 -1
- warp/examples/benchmarks/benchmark_launches.py +0 -2
- warp/examples/core/example_dem.py +0 -2
- warp/examples/core/example_fluid.py +0 -2
- warp/examples/core/example_graph_capture.py +0 -2
- warp/examples/core/example_marching_cubes.py +0 -2
- warp/examples/core/example_mesh.py +0 -2
- warp/examples/core/example_mesh_intersect.py +0 -2
- warp/examples/core/example_nvdb.py +0 -2
- warp/examples/core/example_raycast.py +0 -2
- warp/examples/core/example_raymarch.py +0 -2
- warp/examples/core/example_render_opengl.py +0 -2
- warp/examples/core/example_sph.py +0 -2
- warp/examples/core/example_torch.py +0 -3
- warp/examples/core/example_wave.py +0 -2
- warp/examples/fem/example_apic_fluid.py +140 -115
- warp/examples/fem/example_burgers.py +262 -0
- warp/examples/fem/example_convection_diffusion.py +0 -2
- warp/examples/fem/example_convection_diffusion_dg.py +0 -2
- warp/examples/fem/example_deformed_geometry.py +0 -2
- warp/examples/fem/example_diffusion.py +0 -2
- warp/examples/fem/example_diffusion_3d.py +5 -4
- warp/examples/fem/example_diffusion_mgpu.py +0 -2
- warp/examples/fem/example_mixed_elasticity.py +0 -2
- warp/examples/fem/example_navier_stokes.py +0 -2
- warp/examples/fem/example_stokes.py +0 -2
- warp/examples/fem/example_stokes_transfer.py +0 -2
- warp/examples/optim/example_bounce.py +0 -2
- warp/examples/optim/example_cloth_throw.py +0 -2
- warp/examples/optim/example_diffray.py +0 -2
- warp/examples/optim/example_drone.py +0 -2
- warp/examples/optim/example_inverse_kinematics.py +0 -2
- warp/examples/optim/example_inverse_kinematics_torch.py +0 -2
- warp/examples/optim/example_spring_cage.py +0 -2
- warp/examples/optim/example_trajectory.py +0 -2
- warp/examples/optim/example_walker.py +0 -2
- warp/examples/sim/example_cartpole.py +0 -2
- warp/examples/sim/example_cloth.py +0 -2
- warp/examples/sim/example_granular.py +0 -2
- warp/examples/sim/example_granular_collision_sdf.py +0 -2
- warp/examples/sim/example_jacobian_ik.py +0 -2
- warp/examples/sim/example_particle_chain.py +0 -2
- warp/examples/sim/example_quadruped.py +0 -2
- warp/examples/sim/example_rigid_chain.py +0 -2
- warp/examples/sim/example_rigid_contact.py +0 -2
- warp/examples/sim/example_rigid_force.py +0 -2
- warp/examples/sim/example_rigid_gyroscopic.py +0 -2
- warp/examples/sim/example_rigid_soft_contact.py +0 -2
- warp/examples/sim/example_soft_body.py +0 -2
- warp/fem/__init__.py +1 -0
- warp/fem/cache.py +3 -1
- warp/fem/geometry/__init__.py +1 -0
- warp/fem/geometry/element.py +4 -0
- warp/fem/geometry/grid_3d.py +0 -4
- warp/fem/geometry/nanogrid.py +455 -0
- warp/fem/integrate.py +63 -9
- warp/fem/space/__init__.py +43 -158
- warp/fem/space/basis_space.py +34 -0
- warp/fem/space/collocated_function_space.py +1 -1
- warp/fem/space/grid_2d_function_space.py +13 -132
- warp/fem/space/grid_3d_function_space.py +16 -154
- warp/fem/space/hexmesh_function_space.py +37 -134
- warp/fem/space/nanogrid_function_space.py +202 -0
- warp/fem/space/quadmesh_2d_function_space.py +12 -119
- warp/fem/space/restriction.py +4 -1
- warp/fem/space/shape/__init__.py +77 -0
- warp/fem/space/shape/cube_shape_function.py +5 -15
- warp/fem/space/tetmesh_function_space.py +6 -76
- warp/fem/space/trimesh_2d_function_space.py +6 -76
- warp/native/array.h +12 -3
- warp/native/builtin.h +48 -5
- warp/native/bvh.cpp +14 -10
- warp/native/bvh.cu +23 -15
- warp/native/bvh.h +1 -0
- warp/native/clang/clang.cpp +2 -1
- warp/native/crt.cpp +11 -1
- warp/native/crt.h +18 -1
- warp/native/exports.h +187 -0
- warp/native/mat.h +47 -0
- warp/native/mesh.cpp +1 -1
- warp/native/mesh.cu +1 -2
- warp/native/nanovdb/GridHandle.h +366 -0
- warp/native/nanovdb/HostBuffer.h +590 -0
- warp/native/nanovdb/NanoVDB.h +3999 -2157
- warp/native/nanovdb/PNanoVDB.h +936 -99
- warp/native/quat.h +28 -1
- warp/native/rand.h +5 -1
- warp/native/vec.h +45 -1
- warp/native/volume.cpp +335 -103
- warp/native/volume.cu +39 -13
- warp/native/volume.h +725 -303
- warp/native/volume_builder.cu +381 -360
- warp/native/volume_builder.h +16 -1
- warp/native/volume_impl.h +61 -0
- warp/native/warp.cu +8 -2
- warp/native/warp.h +15 -7
- warp/render/render_opengl.py +191 -52
- warp/sim/integrator_featherstone.py +10 -3
- warp/sim/integrator_xpbd.py +16 -22
- warp/sparse.py +89 -27
- warp/stubs.py +83 -0
- warp/tests/assets/test_index_grid.nvdb +0 -0
- warp/tests/aux_test_dependent.py +0 -2
- warp/tests/aux_test_grad_customs.py +0 -2
- warp/tests/aux_test_reference.py +0 -2
- warp/tests/aux_test_reference_reference.py +0 -2
- warp/tests/aux_test_square.py +0 -2
- warp/tests/disabled_kinematics.py +0 -2
- warp/tests/test_adam.py +0 -2
- warp/tests/test_arithmetic.py +0 -36
- warp/tests/test_array.py +9 -11
- warp/tests/test_array_reduce.py +0 -2
- warp/tests/test_async.py +0 -2
- warp/tests/test_atomic.py +0 -2
- warp/tests/test_bool.py +58 -50
- warp/tests/test_builtins_resolution.py +0 -2
- warp/tests/test_bvh.py +0 -2
- warp/tests/test_closest_point_edge_edge.py +0 -1
- warp/tests/test_codegen.py +0 -4
- warp/tests/test_compile_consts.py +130 -10
- warp/tests/test_conditional.py +0 -2
- warp/tests/test_copy.py +0 -2
- warp/tests/test_ctypes.py +6 -8
- warp/tests/test_dense.py +0 -2
- warp/tests/test_devices.py +0 -2
- warp/tests/test_dlpack.py +9 -11
- warp/tests/test_examples.py +42 -39
- warp/tests/test_fabricarray.py +0 -3
- warp/tests/test_fast_math.py +0 -2
- warp/tests/test_fem.py +75 -54
- warp/tests/test_fp16.py +0 -2
- warp/tests/test_func.py +0 -2
- warp/tests/test_generics.py +27 -2
- warp/tests/test_grad.py +147 -8
- warp/tests/test_grad_customs.py +0 -2
- warp/tests/test_hash_grid.py +1 -3
- warp/tests/test_import.py +0 -2
- warp/tests/test_indexedarray.py +0 -2
- warp/tests/test_intersect.py +0 -2
- warp/tests/test_jax.py +0 -2
- warp/tests/test_large.py +11 -9
- warp/tests/test_launch.py +0 -2
- warp/tests/test_lerp.py +10 -54
- warp/tests/test_linear_solvers.py +3 -5
- warp/tests/test_lvalue.py +0 -2
- warp/tests/test_marching_cubes.py +0 -2
- warp/tests/test_mat.py +0 -2
- warp/tests/test_mat_lite.py +0 -2
- warp/tests/test_mat_scalar_ops.py +0 -2
- warp/tests/test_math.py +0 -2
- warp/tests/test_matmul.py +35 -37
- warp/tests/test_matmul_lite.py +29 -31
- warp/tests/test_mempool.py +0 -2
- warp/tests/test_mesh.py +0 -3
- warp/tests/test_mesh_query_aabb.py +0 -2
- warp/tests/test_mesh_query_point.py +0 -2
- warp/tests/test_mesh_query_ray.py +0 -2
- warp/tests/test_mlp.py +0 -2
- warp/tests/test_model.py +0 -2
- warp/tests/test_module_hashing.py +111 -0
- warp/tests/test_modules_lite.py +0 -3
- warp/tests/test_multigpu.py +0 -2
- warp/tests/test_noise.py +0 -4
- warp/tests/test_operators.py +0 -2
- warp/tests/test_options.py +0 -2
- warp/tests/test_peer.py +0 -2
- warp/tests/test_pinned.py +0 -2
- warp/tests/test_print.py +0 -2
- warp/tests/test_quat.py +0 -2
- warp/tests/test_rand.py +41 -5
- warp/tests/test_reload.py +0 -10
- warp/tests/test_rounding.py +0 -2
- warp/tests/test_runlength_encode.py +0 -2
- warp/tests/test_sim_grad.py +0 -2
- warp/tests/test_sim_kinematics.py +0 -2
- warp/tests/test_smoothstep.py +0 -2
- warp/tests/test_snippet.py +0 -2
- warp/tests/test_sparse.py +0 -2
- warp/tests/test_spatial.py +0 -2
- warp/tests/test_special_values.py +362 -0
- warp/tests/test_streams.py +0 -2
- warp/tests/test_struct.py +0 -2
- warp/tests/test_tape.py +0 -2
- warp/tests/test_torch.py +0 -2
- warp/tests/test_transient_module.py +0 -2
- warp/tests/test_types.py +0 -2
- warp/tests/test_utils.py +0 -2
- warp/tests/test_vec.py +0 -2
- warp/tests/test_vec_lite.py +0 -2
- warp/tests/test_vec_scalar_ops.py +0 -2
- warp/tests/test_verify_fp.py +0 -2
- warp/tests/test_volume.py +237 -13
- warp/tests/test_volume_write.py +86 -3
- warp/tests/unittest_serial.py +10 -9
- warp/tests/unittest_suites.py +6 -2
- warp/tests/unittest_utils.py +2 -171
- warp/tests/unused_test_misc.py +0 -2
- warp/tests/walkthrough_debug.py +1 -1
- warp/thirdparty/unittest_parallel.py +37 -40
- warp/types.py +526 -85
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.1.dist-info}/METADATA +61 -31
- warp_lang-1.2.1.dist-info/RECORD +359 -0
- warp/examples/fem/example_convection_diffusion_dg0.py +0 -204
- warp/native/nanovdb/PNanoVDBWrite.h +0 -295
- warp_lang-1.1.0.dist-info/RECORD +0 -352
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.1.dist-info}/LICENSE.md +0 -0
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.1.dist-info}/WHEEL +0 -0
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.1.dist-info}/top_level.txt +0 -0
warp/types.py
CHANGED
|
@@ -9,11 +9,10 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import builtins
|
|
11
11
|
import ctypes
|
|
12
|
-
import hashlib
|
|
13
12
|
import inspect
|
|
14
13
|
import struct
|
|
15
14
|
import zlib
|
|
16
|
-
from typing import Any, Callable, Generic, List, Tuple, TypeVar, Union
|
|
15
|
+
from typing import Any, Callable, Generic, List, NamedTuple, Optional, Tuple, TypeVar, Union
|
|
17
16
|
|
|
18
17
|
import numpy as np
|
|
19
18
|
|
|
@@ -51,10 +50,6 @@ class Array(Generic[DType]):
|
|
|
51
50
|
pass
|
|
52
51
|
|
|
53
52
|
|
|
54
|
-
# shared hash for all constants
|
|
55
|
-
_constant_hash = hashlib.sha256()
|
|
56
|
-
|
|
57
|
-
|
|
58
53
|
def constant(x):
|
|
59
54
|
"""Function to declare compile-time constants accessible from Warp kernels
|
|
60
55
|
|
|
@@ -62,27 +57,7 @@ def constant(x):
|
|
|
62
57
|
x: Compile-time constant value, can be any of the built-in math types.
|
|
63
58
|
"""
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# hash the constant value
|
|
68
|
-
if isinstance(x, builtins.bool):
|
|
69
|
-
# This needs to come before the check for `int` since all boolean
|
|
70
|
-
# values are also instances of `int`.
|
|
71
|
-
_constant_hash.update(struct.pack("?", x))
|
|
72
|
-
elif isinstance(x, int):
|
|
73
|
-
_constant_hash.update(struct.pack("<q", x))
|
|
74
|
-
elif isinstance(x, float):
|
|
75
|
-
_constant_hash.update(struct.pack("<d", x))
|
|
76
|
-
elif isinstance(x, float16):
|
|
77
|
-
# float16 is a special case
|
|
78
|
-
p = ctypes.pointer(ctypes.c_float(x.value))
|
|
79
|
-
_constant_hash.update(p.contents)
|
|
80
|
-
elif isinstance(x, tuple(scalar_types)):
|
|
81
|
-
p = ctypes.pointer(x._type_(x.value))
|
|
82
|
-
_constant_hash.update(p.contents)
|
|
83
|
-
elif isinstance(x, ctypes.Array):
|
|
84
|
-
_constant_hash.update(bytes(x))
|
|
85
|
-
else:
|
|
60
|
+
if not isinstance(x, (builtins.bool, int, float, tuple(scalar_and_bool_types), ctypes.Array)):
|
|
86
61
|
raise RuntimeError(f"Invalid constant type: {type(x)}")
|
|
87
62
|
|
|
88
63
|
return x
|
|
@@ -503,7 +478,7 @@ class bool:
|
|
|
503
478
|
def __init__(self, x=False):
|
|
504
479
|
self.value = x
|
|
505
480
|
|
|
506
|
-
def __bool__(self) -> bool:
|
|
481
|
+
def __bool__(self) -> builtins.bool:
|
|
507
482
|
return self.value != 0
|
|
508
483
|
|
|
509
484
|
def __float__(self) -> float:
|
|
@@ -1675,9 +1650,10 @@ class array(Array):
|
|
|
1675
1650
|
try:
|
|
1676
1651
|
# Performance note: try first, ask questions later
|
|
1677
1652
|
device = warp.context.runtime.get_device(device)
|
|
1678
|
-
except:
|
|
1679
|
-
|
|
1680
|
-
|
|
1653
|
+
except Exception:
|
|
1654
|
+
# Fallback to using the public API for retrieving the device,
|
|
1655
|
+
# which takes take of initializing Warp if needed.
|
|
1656
|
+
device = warp.context.get_device(device)
|
|
1681
1657
|
|
|
1682
1658
|
if device.is_cuda:
|
|
1683
1659
|
desc = data.__cuda_array_interface__
|
|
@@ -1804,9 +1780,10 @@ class array(Array):
|
|
|
1804
1780
|
try:
|
|
1805
1781
|
# Performance note: try first, ask questions later
|
|
1806
1782
|
device = warp.context.runtime.get_device(device)
|
|
1807
|
-
except:
|
|
1808
|
-
|
|
1809
|
-
|
|
1783
|
+
except Exception:
|
|
1784
|
+
# Fallback to using the public API for retrieving the device,
|
|
1785
|
+
# which takes take of initializing Warp if needed.
|
|
1786
|
+
device = warp.context.get_device(device)
|
|
1810
1787
|
|
|
1811
1788
|
if device.is_cpu and not copy and not pinned:
|
|
1812
1789
|
# reference numpy memory directly
|
|
@@ -1830,9 +1807,10 @@ class array(Array):
|
|
|
1830
1807
|
try:
|
|
1831
1808
|
# Performance note: try first, ask questions later
|
|
1832
1809
|
device = warp.context.runtime.get_device(device)
|
|
1833
|
-
except:
|
|
1834
|
-
|
|
1835
|
-
|
|
1810
|
+
except Exception:
|
|
1811
|
+
# Fallback to using the public API for retrieving the device,
|
|
1812
|
+
# which takes take of initializing Warp if needed.
|
|
1813
|
+
device = warp.context.get_device(device)
|
|
1836
1814
|
|
|
1837
1815
|
check_array_shape(shape)
|
|
1838
1816
|
ndim = len(shape)
|
|
@@ -1877,9 +1855,10 @@ class array(Array):
|
|
|
1877
1855
|
try:
|
|
1878
1856
|
# Performance note: try first, ask questions later
|
|
1879
1857
|
device = warp.context.runtime.get_device(device)
|
|
1880
|
-
except:
|
|
1881
|
-
|
|
1882
|
-
|
|
1858
|
+
except Exception:
|
|
1859
|
+
# Fallback to using the public API for retrieving the device,
|
|
1860
|
+
# which takes take of initializing Warp if needed.
|
|
1861
|
+
device = warp.context.get_device(device)
|
|
1883
1862
|
|
|
1884
1863
|
check_array_shape(shape)
|
|
1885
1864
|
ndim = len(shape)
|
|
@@ -3022,11 +3001,12 @@ class Volume:
|
|
|
3022
3001
|
#: Enum value to specify trilinear interpolation during sampling
|
|
3023
3002
|
LINEAR = constant(1)
|
|
3024
3003
|
|
|
3025
|
-
def __init__(self, data: array):
|
|
3004
|
+
def __init__(self, data: array, copy: bool = True):
|
|
3026
3005
|
"""Class representing a sparse grid.
|
|
3027
3006
|
|
|
3028
3007
|
Args:
|
|
3029
3008
|
data (:class:`warp.array`): Array of bytes representing the volume in NanoVDB format
|
|
3009
|
+
copy (bool): Whether the incoming data will be copied or aliased
|
|
3030
3010
|
"""
|
|
3031
3011
|
|
|
3032
3012
|
self.id = 0
|
|
@@ -3036,16 +3016,16 @@ class Volume:
|
|
|
3036
3016
|
|
|
3037
3017
|
if data is None:
|
|
3038
3018
|
return
|
|
3039
|
-
|
|
3040
|
-
if data.device is None:
|
|
3041
|
-
raise RuntimeError("Invalid device")
|
|
3042
3019
|
self.device = data.device
|
|
3043
3020
|
|
|
3021
|
+
owner = False
|
|
3044
3022
|
if self.device.is_cpu:
|
|
3045
|
-
self.id = self.runtime.core.volume_create_host(
|
|
3023
|
+
self.id = self.runtime.core.volume_create_host(
|
|
3024
|
+
ctypes.cast(data.ptr, ctypes.c_void_p), data.size, copy, owner
|
|
3025
|
+
)
|
|
3046
3026
|
else:
|
|
3047
3027
|
self.id = self.runtime.core.volume_create_device(
|
|
3048
|
-
self.device.context, ctypes.cast(data.ptr, ctypes.c_void_p), data.size
|
|
3028
|
+
self.device.context, ctypes.cast(data.ptr, ctypes.c_void_p), data.size, copy, owner
|
|
3049
3029
|
)
|
|
3050
3030
|
|
|
3051
3031
|
if self.id == 0:
|
|
@@ -3066,32 +3046,90 @@ class Volume:
|
|
|
3066
3046
|
"""Returns the raw memory buffer of the Volume as an array"""
|
|
3067
3047
|
buf = ctypes.c_void_p(0)
|
|
3068
3048
|
size = ctypes.c_uint64(0)
|
|
3049
|
+
self.runtime.core.volume_get_buffer_info(self.id, ctypes.byref(buf), ctypes.byref(size))
|
|
3050
|
+
return array(ptr=buf.value, dtype=uint8, shape=size.value, device=self.device, owner=False)
|
|
3051
|
+
|
|
3052
|
+
def get_tile_count(self) -> int:
|
|
3053
|
+
"""Returns the number of tiles (NanoVDB leaf nodes) of the volume"""
|
|
3054
|
+
|
|
3055
|
+
voxel_count, tile_count = (
|
|
3056
|
+
ctypes.c_uint64(0),
|
|
3057
|
+
ctypes.c_uint32(0),
|
|
3058
|
+
)
|
|
3059
|
+
self.runtime.core.volume_get_tile_and_voxel_count(self.id, ctypes.byref(tile_count), ctypes.byref(voxel_count))
|
|
3060
|
+
return tile_count.value
|
|
3061
|
+
|
|
3062
|
+
def get_tiles(self, out: Optional[array] = None) -> array:
|
|
3063
|
+
"""Returns the integer coordinates of all allocated tiles for this volume.
|
|
3064
|
+
|
|
3065
|
+
Args:
|
|
3066
|
+
out (:class:`warp.array`, optional): If provided, use the `out` array to store the tile coordinates, otherwise
|
|
3067
|
+
a new array will be allocated. `out` must be a contiguous array of ``tile_count`` ``vec3i`` or ``tile_count x 3`` ``int32``
|
|
3068
|
+
on the same device as this volume.
|
|
3069
|
+
"""
|
|
3070
|
+
|
|
3071
|
+
if self.id == 0:
|
|
3072
|
+
raise RuntimeError("Invalid Volume")
|
|
3073
|
+
|
|
3074
|
+
tile_count = self.get_tile_count()
|
|
3075
|
+
if out is None:
|
|
3076
|
+
out = warp.empty(dtype=int32, shape=(tile_count, 3), device=self.device)
|
|
3077
|
+
elif out.device != self.device or out.shape[0] < tile_count:
|
|
3078
|
+
raise RuntimeError(f"'out' array must an array with at least {tile_count} rows on device {self.device}")
|
|
3079
|
+
elif not _is_contiguous_vec_like_array(out, vec_length=3, scalar_types=(int32,)):
|
|
3080
|
+
raise RuntimeError(
|
|
3081
|
+
"'out' must be a contiguous 1D array with type vec3i or a 2D array of type int32 with shape (N, 3) "
|
|
3082
|
+
)
|
|
3083
|
+
|
|
3069
3084
|
if self.device.is_cpu:
|
|
3070
|
-
self.runtime.core.
|
|
3085
|
+
self.runtime.core.volume_get_tiles_host(self.id, out.ptr)
|
|
3071
3086
|
else:
|
|
3072
|
-
self.runtime.core.
|
|
3073
|
-
|
|
3087
|
+
self.runtime.core.volume_get_tiles_device(self.id, out.ptr)
|
|
3088
|
+
|
|
3089
|
+
return out
|
|
3090
|
+
|
|
3091
|
+
def get_voxel_count(self) -> int:
|
|
3092
|
+
"""Returns the total number of allocated voxels for this volume"""
|
|
3093
|
+
|
|
3094
|
+
voxel_count, tile_count = (
|
|
3095
|
+
ctypes.c_uint64(0),
|
|
3096
|
+
ctypes.c_uint32(0),
|
|
3097
|
+
)
|
|
3098
|
+
self.runtime.core.volume_get_tile_and_voxel_count(self.id, ctypes.byref(tile_count), ctypes.byref(voxel_count))
|
|
3099
|
+
return voxel_count.value
|
|
3100
|
+
|
|
3101
|
+
def get_voxels(self, out: Optional[array] = None) -> array:
|
|
3102
|
+
"""Returns the integer coordinates of all allocated voxels for this volume.
|
|
3103
|
+
|
|
3104
|
+
Args:
|
|
3105
|
+
out (:class:`warp.array`, optional): If provided, use the `out` array to store the voxel coordinates, otherwise
|
|
3106
|
+
a new array will be allocated. `out` must be a contiguous array of ``voxel_count`` ``vec3i`` or ``voxel_count x 3`` ``int32``
|
|
3107
|
+
on the same device as this volume.
|
|
3108
|
+
"""
|
|
3074
3109
|
|
|
3075
|
-
def get_tiles(self) -> array:
|
|
3076
3110
|
if self.id == 0:
|
|
3077
3111
|
raise RuntimeError("Invalid Volume")
|
|
3078
3112
|
|
|
3079
|
-
|
|
3080
|
-
|
|
3113
|
+
voxel_count = self.get_voxel_count()
|
|
3114
|
+
if out is None:
|
|
3115
|
+
out = warp.empty(dtype=int32, shape=(voxel_count, 3), device=self.device)
|
|
3116
|
+
elif out.device != self.device or out.shape[0] < voxel_count:
|
|
3117
|
+
raise RuntimeError(f"'out' array must an array with at least {voxel_count} rows on device {self.device}")
|
|
3118
|
+
elif not _is_contiguous_vec_like_array(out, vec_length=3, scalar_types=(int32,)):
|
|
3119
|
+
raise RuntimeError(
|
|
3120
|
+
"'out' must be a contiguous 1D array with type vec3i or a 2D array of type int32 with shape (N, 3) "
|
|
3121
|
+
)
|
|
3122
|
+
|
|
3081
3123
|
if self.device.is_cpu:
|
|
3082
|
-
self.runtime.core.
|
|
3083
|
-
deleter = self.device.default_allocator.deleter
|
|
3124
|
+
self.runtime.core.volume_get_voxels_host(self.id, out.ptr)
|
|
3084
3125
|
else:
|
|
3085
|
-
self.runtime.core.
|
|
3086
|
-
if self.device.is_mempool_supported:
|
|
3087
|
-
deleter = self.device.mempool_allocator.deleter
|
|
3088
|
-
else:
|
|
3089
|
-
deleter = self.device.default_allocator.deleter
|
|
3090
|
-
num_tiles = size.value // (3 * 4)
|
|
3126
|
+
self.runtime.core.volume_get_voxels_device(self.id, out.ptr)
|
|
3091
3127
|
|
|
3092
|
-
return
|
|
3128
|
+
return out
|
|
3093
3129
|
|
|
3094
3130
|
def get_voxel_size(self) -> Tuple[float, float, float]:
|
|
3131
|
+
"""Voxel size, i.e, world coordinates of voxel's diagonal vector"""
|
|
3132
|
+
|
|
3095
3133
|
if self.id == 0:
|
|
3096
3134
|
raise RuntimeError("Invalid Volume")
|
|
3097
3135
|
|
|
@@ -3099,9 +3137,181 @@ class Volume:
|
|
|
3099
3137
|
self.runtime.core.volume_get_voxel_size(self.id, ctypes.byref(dx), ctypes.byref(dy), ctypes.byref(dz))
|
|
3100
3138
|
return (dx.value, dy.value, dz.value)
|
|
3101
3139
|
|
|
3140
|
+
class GridInfo(NamedTuple):
|
|
3141
|
+
"""Grid metadata"""
|
|
3142
|
+
|
|
3143
|
+
name: str
|
|
3144
|
+
"""Grid name"""
|
|
3145
|
+
size_in_bytes: int
|
|
3146
|
+
"""Size of this grid's data, in bytes"""
|
|
3147
|
+
|
|
3148
|
+
grid_index: int
|
|
3149
|
+
"""Index of this grid in the data buffer"""
|
|
3150
|
+
grid_count: int
|
|
3151
|
+
"""Total number of grids in the data buffer"""
|
|
3152
|
+
type_str: str
|
|
3153
|
+
"""String describing the type of the grid values"""
|
|
3154
|
+
|
|
3155
|
+
translation: vec3f
|
|
3156
|
+
"""Index-to-world translation"""
|
|
3157
|
+
transform_matrix: mat33f
|
|
3158
|
+
"""Linear part of the index-to-world transform"""
|
|
3159
|
+
|
|
3160
|
+
def get_grid_info(self) -> Volume.GridInfo:
|
|
3161
|
+
"""Returns the metadata associated with this Volume"""
|
|
3162
|
+
|
|
3163
|
+
grid_index = ctypes.c_uint32(0)
|
|
3164
|
+
grid_count = ctypes.c_uint32(0)
|
|
3165
|
+
grid_size = ctypes.c_uint64(0)
|
|
3166
|
+
translation_buffer = (ctypes.c_float * 3)()
|
|
3167
|
+
transform_buffer = (ctypes.c_float * 9)()
|
|
3168
|
+
type_str_buffer = (ctypes.c_char * 16)()
|
|
3169
|
+
|
|
3170
|
+
name = self.runtime.core.volume_get_grid_info(
|
|
3171
|
+
self.id,
|
|
3172
|
+
ctypes.byref(grid_size),
|
|
3173
|
+
ctypes.byref(grid_index),
|
|
3174
|
+
ctypes.byref(grid_count),
|
|
3175
|
+
translation_buffer,
|
|
3176
|
+
transform_buffer,
|
|
3177
|
+
type_str_buffer,
|
|
3178
|
+
)
|
|
3179
|
+
|
|
3180
|
+
if name is None:
|
|
3181
|
+
raise RuntimeError("Invalid volume")
|
|
3182
|
+
|
|
3183
|
+
return Volume.GridInfo(
|
|
3184
|
+
name.decode("ascii"),
|
|
3185
|
+
grid_size.value,
|
|
3186
|
+
grid_index.value,
|
|
3187
|
+
grid_count.value,
|
|
3188
|
+
type_str_buffer.value.decode("ascii"),
|
|
3189
|
+
vec3f.from_buffer_copy(translation_buffer),
|
|
3190
|
+
mat33f.from_buffer_copy(transform_buffer),
|
|
3191
|
+
)
|
|
3192
|
+
|
|
3193
|
+
_nvdb_type_to_dtype = {
|
|
3194
|
+
"float": float32,
|
|
3195
|
+
"double": float64,
|
|
3196
|
+
"int16": int16,
|
|
3197
|
+
"int32": int32,
|
|
3198
|
+
"int64": int64,
|
|
3199
|
+
"Vec3f": vec3f,
|
|
3200
|
+
"Vec3d": vec3d,
|
|
3201
|
+
"Half": float16,
|
|
3202
|
+
"uint32": uint32,
|
|
3203
|
+
"bool": bool,
|
|
3204
|
+
"Vec4f": vec4f,
|
|
3205
|
+
"Vec4d": vec4d,
|
|
3206
|
+
"Vec3u8": vec3ub,
|
|
3207
|
+
"Vec3u16": vec3us,
|
|
3208
|
+
"uint8": uint8,
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
@property
|
|
3212
|
+
def dtype(self) -> type:
|
|
3213
|
+
"""Type of the Volume's values as a Warp type.
|
|
3214
|
+
|
|
3215
|
+
If the grid does not contain values (e.g. index grids) or if the NanoVDB type is not
|
|
3216
|
+
representable as a Warp type, returns ``None``.
|
|
3217
|
+
"""
|
|
3218
|
+
return Volume._nvdb_type_to_dtype.get(self.get_grid_info().type_str, None)
|
|
3219
|
+
|
|
3220
|
+
_nvdb_index_types = ("Index", "OnIndex", "IndexMask", "OnIndexMask")
|
|
3221
|
+
|
|
3222
|
+
@property
|
|
3223
|
+
def is_index(self) -> bool:
|
|
3224
|
+
"""Whether this Volume contains an index grid, that is, a type of grid that does
|
|
3225
|
+
not explicitly store values but associates each voxel to linearized index.
|
|
3226
|
+
"""
|
|
3227
|
+
|
|
3228
|
+
return self.get_grid_info().type_str in Volume._nvdb_index_types
|
|
3229
|
+
|
|
3230
|
+
def get_feature_array_count(self) -> int:
|
|
3231
|
+
"""Returns the number of supplemental data arrays stored alongside the grid"""
|
|
3232
|
+
|
|
3233
|
+
return self.runtime.core.volume_get_blind_data_count(self.id)
|
|
3234
|
+
|
|
3235
|
+
class FeatureArrayInfo(NamedTuple):
|
|
3236
|
+
"""Metadata for a supplemental data array"""
|
|
3237
|
+
|
|
3238
|
+
name: str
|
|
3239
|
+
"""Name of the data array"""
|
|
3240
|
+
ptr: int
|
|
3241
|
+
"""Memory address of the start of the array"""
|
|
3242
|
+
|
|
3243
|
+
value_size: int
|
|
3244
|
+
"""Size in bytes of the array values"""
|
|
3245
|
+
value_count: int
|
|
3246
|
+
"""Number of values in the array"""
|
|
3247
|
+
type_str: str
|
|
3248
|
+
"""String describing the type of the array values"""
|
|
3249
|
+
|
|
3250
|
+
def get_feature_array_info(self, feature_index: int) -> Volume.FeatureArrayInfo:
|
|
3251
|
+
"""Returns the metadata associated to the feature array at `feature_index`"""
|
|
3252
|
+
|
|
3253
|
+
buf = ctypes.c_void_p(0)
|
|
3254
|
+
value_count = ctypes.c_uint64(0)
|
|
3255
|
+
value_size = ctypes.c_uint32(0)
|
|
3256
|
+
type_str_buffer = (ctypes.c_char * 16)()
|
|
3257
|
+
|
|
3258
|
+
name = self.runtime.core.volume_get_blind_data_info(
|
|
3259
|
+
self.id,
|
|
3260
|
+
feature_index,
|
|
3261
|
+
ctypes.byref(buf),
|
|
3262
|
+
ctypes.byref(value_count),
|
|
3263
|
+
ctypes.byref(value_size),
|
|
3264
|
+
type_str_buffer,
|
|
3265
|
+
)
|
|
3266
|
+
|
|
3267
|
+
if buf.value is None:
|
|
3268
|
+
raise RuntimeError("Invalid feature array")
|
|
3269
|
+
|
|
3270
|
+
return Volume.FeatureArrayInfo(
|
|
3271
|
+
name.decode("ascii"),
|
|
3272
|
+
buf.value,
|
|
3273
|
+
value_size.value,
|
|
3274
|
+
value_count.value,
|
|
3275
|
+
type_str_buffer.value.decode("ascii"),
|
|
3276
|
+
)
|
|
3277
|
+
|
|
3278
|
+
def feature_array(self, feature_index: int, dtype=None) -> array:
|
|
3279
|
+
"""Returns one the the grid's feature data arrays as a Warp array
|
|
3280
|
+
|
|
3281
|
+
Args:
|
|
3282
|
+
feature_index: Index of the supplemental data array in the grid
|
|
3283
|
+
dtype: Type for the returned Warp array. If not provided, will be deduced from the array metadata.
|
|
3284
|
+
"""
|
|
3285
|
+
|
|
3286
|
+
info = self.get_feature_array_info(feature_index)
|
|
3287
|
+
|
|
3288
|
+
if dtype is None:
|
|
3289
|
+
try:
|
|
3290
|
+
dtype = Volume._nvdb_type_to_dtype[info.type_str]
|
|
3291
|
+
except KeyError:
|
|
3292
|
+
# Unknown type, default to byte array
|
|
3293
|
+
dtype = uint8
|
|
3294
|
+
|
|
3295
|
+
value_count = info.value_count
|
|
3296
|
+
value_size = info.value_size
|
|
3297
|
+
|
|
3298
|
+
if type_size_in_bytes(dtype) == 1:
|
|
3299
|
+
# allow requesting a byte array from any type
|
|
3300
|
+
value_count *= value_size
|
|
3301
|
+
value_size = 1
|
|
3302
|
+
elif value_size == 1 and (value_count % type_size_in_bytes(dtype)) == 0:
|
|
3303
|
+
# allow converting a byte array to any type
|
|
3304
|
+
value_size = type_size_in_bytes(dtype)
|
|
3305
|
+
value_count = value_count // value_size
|
|
3306
|
+
|
|
3307
|
+
if type_size_in_bytes(dtype) != value_size:
|
|
3308
|
+
raise RuntimeError(f"Cannot cast feature data of size {value_size} to array dtype {type_repr(dtype)}")
|
|
3309
|
+
|
|
3310
|
+
return array(ptr=info.ptr, dtype=dtype, shape=value_count, device=self.device, owner=False)
|
|
3311
|
+
|
|
3102
3312
|
@classmethod
|
|
3103
3313
|
def load_from_nvdb(cls, file_or_buffer, device=None) -> Volume:
|
|
3104
|
-
"""Creates a Volume object from a NanoVDB file or in-memory buffer.
|
|
3314
|
+
"""Creates a Volume object from a serialized NanoVDB file or in-memory buffer.
|
|
3105
3315
|
|
|
3106
3316
|
Returns:
|
|
3107
3317
|
|
|
@@ -3113,28 +3323,117 @@ class Volume:
|
|
|
3113
3323
|
data = file_or_buffer
|
|
3114
3324
|
|
|
3115
3325
|
magic, version, grid_count, codec = struct.unpack("<QIHH", data[0:16])
|
|
3116
|
-
if magic
|
|
3326
|
+
if magic not in (0x304244566F6E614E, 0x324244566F6E614E): # NanoVDB0 or NanoVDB2 in hex, little-endian
|
|
3117
3327
|
raise RuntimeError("NanoVDB signature not found")
|
|
3118
3328
|
if version >> 21 != 32: # checking major version
|
|
3119
3329
|
raise RuntimeError("Unsupported NanoVDB version")
|
|
3120
|
-
if grid_count != 1:
|
|
3121
|
-
raise RuntimeError("Only NVDBs with exactly one grid are supported")
|
|
3122
3330
|
|
|
3123
|
-
|
|
3331
|
+
# Skip over segment metadata, store total payload size
|
|
3332
|
+
grid_data_offset = 16 # sizeof(FileHeader)
|
|
3333
|
+
tot_file_size = 0
|
|
3334
|
+
for _ in range(grid_count):
|
|
3335
|
+
grid_file_size = struct.unpack("<Q", data[grid_data_offset + 8 : grid_data_offset + 16])[0]
|
|
3336
|
+
tot_file_size += grid_file_size
|
|
3337
|
+
|
|
3338
|
+
grid_name_size = struct.unpack("<I", data[grid_data_offset + 136 : grid_data_offset + 140])[0]
|
|
3339
|
+
grid_data_offset += 176 + grid_name_size # sizeof(FileMetadata) + grid name
|
|
3340
|
+
|
|
3341
|
+
file_end = grid_data_offset + tot_file_size
|
|
3342
|
+
|
|
3124
3343
|
if codec == 0: # no compression
|
|
3125
|
-
grid_data = data[grid_data_offset:]
|
|
3344
|
+
grid_data = data[grid_data_offset:file_end]
|
|
3126
3345
|
elif codec == 1: # zip compression
|
|
3127
|
-
grid_data =
|
|
3346
|
+
grid_data = bytearray()
|
|
3347
|
+
while grid_data_offset < file_end:
|
|
3348
|
+
chunk_size = struct.unpack("<Q", data[grid_data_offset : grid_data_offset + 8])[0]
|
|
3349
|
+
grid_data += zlib.decompress(data[grid_data_offset + 8 :])
|
|
3350
|
+
grid_data_offset += 8 + chunk_size
|
|
3351
|
+
|
|
3352
|
+
elif codec == 2: # blosc compression
|
|
3353
|
+
try:
|
|
3354
|
+
import blosc
|
|
3355
|
+
except ImportError as err:
|
|
3356
|
+
raise RuntimeError(
|
|
3357
|
+
f"NanoVDB buffer is compressed using blosc, but Python module could not be imported: {err}"
|
|
3358
|
+
) from err
|
|
3359
|
+
|
|
3360
|
+
grid_data = bytearray()
|
|
3361
|
+
while grid_data_offset < file_end:
|
|
3362
|
+
chunk_size = struct.unpack("<Q", data[grid_data_offset : grid_data_offset + 8])[0]
|
|
3363
|
+
grid_data += blosc.decompress(data[grid_data_offset + 8 :])
|
|
3364
|
+
grid_data_offset += 8 + chunk_size
|
|
3128
3365
|
else:
|
|
3129
3366
|
raise RuntimeError(f"Unsupported codec code: {codec}")
|
|
3130
3367
|
|
|
3131
3368
|
magic = struct.unpack("<Q", grid_data[0:8])[0]
|
|
3132
|
-
if magic
|
|
3369
|
+
if magic not in (0x304244566F6E614E, 0x314244566F6E614E): # NanoVDB0 or NanoVDB1 in hex, little-endian
|
|
3133
3370
|
raise RuntimeError("NanoVDB signature not found on grid!")
|
|
3134
3371
|
|
|
3135
3372
|
data_array = array(np.frombuffer(grid_data, dtype=np.byte), device=device)
|
|
3136
3373
|
return cls(data_array)
|
|
3137
3374
|
|
|
3375
|
+
@classmethod
|
|
3376
|
+
def load_from_address(cls, grid_ptr: int, buffer_size: int = 0, device=None) -> Volume:
|
|
3377
|
+
"""
|
|
3378
|
+
Creates a new :class:`Volume` aliasing an in-memory grid buffer.
|
|
3379
|
+
|
|
3380
|
+
In contrast to :meth:`load_from_nvdb` which should be used to load serialized NanoVDB grids,
|
|
3381
|
+
here the buffer must be uncompressed and must not contain file header information.
|
|
3382
|
+
If the passed address does not contain a NanoVDB grid, the behavior of this function is undefined.
|
|
3383
|
+
|
|
3384
|
+
Args:
|
|
3385
|
+
grid_ptr: Integer address of the start of the grid buffer
|
|
3386
|
+
buffer_size: Size of the buffer, in bytes. If not provided, the size will be assumed to be that of the single grid starting at `grid_ptr`.
|
|
3387
|
+
device: Device of the buffer, and of the returned Volume. If not provided, the current Warp device is assumed.
|
|
3388
|
+
|
|
3389
|
+
Returns the newly created Volume.
|
|
3390
|
+
"""
|
|
3391
|
+
|
|
3392
|
+
if not grid_ptr:
|
|
3393
|
+
raise (RuntimeError, "Invalid grid buffer pointer")
|
|
3394
|
+
|
|
3395
|
+
# Check that a Volume has not already been created for this address
|
|
3396
|
+
# (to allow this we would need to ref-count the volume descriptor)
|
|
3397
|
+
existing_buf = ctypes.c_void_p(0)
|
|
3398
|
+
existing_size = ctypes.c_uint64(0)
|
|
3399
|
+
warp.context.runtime.core.volume_get_buffer_info(
|
|
3400
|
+
grid_ptr, ctypes.byref(existing_buf), ctypes.byref(existing_size)
|
|
3401
|
+
)
|
|
3402
|
+
|
|
3403
|
+
if existing_buf.value is not None:
|
|
3404
|
+
raise RuntimeError(
|
|
3405
|
+
"A warp Volume has already been created for this grid, aliasing it more than once is not possible."
|
|
3406
|
+
)
|
|
3407
|
+
|
|
3408
|
+
data_array = array(ptr=grid_ptr, dtype=uint8, shape=buffer_size, owner=False, device=device)
|
|
3409
|
+
|
|
3410
|
+
return cls(data_array, copy=False)
|
|
3411
|
+
|
|
3412
|
+
def load_next_grid(self) -> Volume:
|
|
3413
|
+
"""
|
|
3414
|
+
Tries to create a new warp Volume for the next grid that is linked to by this Volume.
|
|
3415
|
+
|
|
3416
|
+
The existence of a next grid is deduced from the `grid_index` and `grid_count` metadata
|
|
3417
|
+
as well as the size of this Volume's in-memory buffer.
|
|
3418
|
+
|
|
3419
|
+
Returns the newly created Volume, or None if there is no next grid.
|
|
3420
|
+
"""
|
|
3421
|
+
|
|
3422
|
+
grid = self.get_grid_info()
|
|
3423
|
+
|
|
3424
|
+
array = self.array()
|
|
3425
|
+
|
|
3426
|
+
if grid.grid_index + 1 >= grid.grid_count or array.capacity <= grid.size_in_bytes:
|
|
3427
|
+
return None
|
|
3428
|
+
|
|
3429
|
+
next_volume = Volume.load_from_address(
|
|
3430
|
+
array.ptr + grid.size_in_bytes, buffer_size=array.capacity - grid.size_in_bytes, device=self.device
|
|
3431
|
+
)
|
|
3432
|
+
# makes the new Volume keep a reference to the current grid, as we're aliasing its buffer
|
|
3433
|
+
next_volume._previous_grid = self
|
|
3434
|
+
|
|
3435
|
+
return next_volume
|
|
3436
|
+
|
|
3138
3437
|
@classmethod
|
|
3139
3438
|
def load_from_numpy(
|
|
3140
3439
|
cls, ndarray: np.array, min_world=(0.0, 0.0, 0.0), voxel_size=1.0, bg_value=0.0, device=None
|
|
@@ -3286,11 +3585,11 @@ class Volume:
|
|
|
3286
3585
|
|
|
3287
3586
|
Args:
|
|
3288
3587
|
tile_points (:class:`warp.array`): Array of positions that define the tiles to be allocated.
|
|
3289
|
-
The array
|
|
3290
|
-
or
|
|
3588
|
+
The array may use an integer scalar type (2D N-by-3 array of :class:`warp.int32` or 1D array of `warp.vec3i` values), indicating index space positions,
|
|
3589
|
+
or a floating point scalar type (2D N-by-3 array of :class:`warp.float32` or 1D array of `warp.vec3f` values), indicating world space positions.
|
|
3291
3590
|
Repeated points per tile are allowed and will be efficiently deduplicated.
|
|
3292
3591
|
voxel_size (float): Voxel size of the new volume.
|
|
3293
|
-
bg_value (float or
|
|
3592
|
+
bg_value (array-like, float, int or None): Value of unallocated voxels of the volume, also defines the volume's type. A :class:`warp.vec3` volume is created if this is `array-like`, an index volume will be created if `bg_value` is ``None``.
|
|
3294
3593
|
translation (array-like): Translation between the index and world spaces.
|
|
3295
3594
|
device (Devicelike): The CUDA device to create the volume on, e.g.: "cuda" or "cuda:0".
|
|
3296
3595
|
|
|
@@ -3301,19 +3600,28 @@ class Volume:
|
|
|
3301
3600
|
raise RuntimeError(f"Voxel size must be positive! Got {voxel_size}")
|
|
3302
3601
|
if not device.is_cuda:
|
|
3303
3602
|
raise RuntimeError("Only CUDA devices are supported for allocate_by_tiles")
|
|
3304
|
-
if not (
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
):
|
|
3309
|
-
raise RuntimeError("Expected an warp array of vec3s or of n-by-3 int32s as tile_points!")
|
|
3603
|
+
if not _is_contiguous_vec_like_array(tile_points, vec_length=3, scalar_types=(float32, int32)):
|
|
3604
|
+
raise RuntimeError(
|
|
3605
|
+
"tile_points must be contiguous and either a 1D warp array of vec3f or vec3i or a 2D n-by-3 array of int32 or float32."
|
|
3606
|
+
)
|
|
3310
3607
|
if not tile_points.device.is_cuda:
|
|
3311
|
-
tile_points =
|
|
3608
|
+
tile_points = tile_points.to(device)
|
|
3312
3609
|
|
|
3313
3610
|
volume = cls(data=None)
|
|
3314
3611
|
volume.device = device
|
|
3315
|
-
in_world_space = tile_points.dtype ==
|
|
3316
|
-
if
|
|
3612
|
+
in_world_space = type_scalar_type(tile_points.dtype) == float32
|
|
3613
|
+
if bg_value is None:
|
|
3614
|
+
volume.id = volume.runtime.core.volume_index_from_tiles_device(
|
|
3615
|
+
volume.device.context,
|
|
3616
|
+
ctypes.c_void_p(tile_points.ptr),
|
|
3617
|
+
tile_points.shape[0],
|
|
3618
|
+
voxel_size,
|
|
3619
|
+
translation[0],
|
|
3620
|
+
translation[1],
|
|
3621
|
+
translation[2],
|
|
3622
|
+
in_world_space,
|
|
3623
|
+
)
|
|
3624
|
+
elif hasattr(bg_value, "__len__"):
|
|
3317
3625
|
volume.id = volume.runtime.core.volume_v_from_tiles_device(
|
|
3318
3626
|
volume.device.context,
|
|
3319
3627
|
ctypes.c_void_p(tile_points.ptr),
|
|
@@ -3357,6 +3665,73 @@ class Volume:
|
|
|
3357
3665
|
|
|
3358
3666
|
return volume
|
|
3359
3667
|
|
|
3668
|
+
@classmethod
|
|
3669
|
+
def allocate_by_voxels(
|
|
3670
|
+
cls, voxel_points: array, voxel_size: float, translation=(0.0, 0.0, 0.0), device=None
|
|
3671
|
+
) -> Volume:
|
|
3672
|
+
"""Allocate a new Volume with active voxel for each point voxel_points.
|
|
3673
|
+
|
|
3674
|
+
This function creates an *index* Volume, a special kind of volume that does not any store any
|
|
3675
|
+
explicit payload but encodes a linearized index for each active voxel, allowing to lookup and
|
|
3676
|
+
sample data from arbitrary external arrays.
|
|
3677
|
+
|
|
3678
|
+
This function is only supported for CUDA devices.
|
|
3679
|
+
|
|
3680
|
+
Args:
|
|
3681
|
+
voxel_points (:class:`warp.array`): Array of positions that define the voxels to be allocated.
|
|
3682
|
+
The array may use an integer scalar type (2D N-by-3 array of :class:`warp.int32` or 1D array of `warp.vec3i` values), indicating index space positions,
|
|
3683
|
+
or a floating point scalar type (2D N-by-3 array of :class:`warp.float32` or 1D array of `warp.vec3f` values), indicating world space positions.
|
|
3684
|
+
Repeated points per tile are allowed and will be efficiently deduplicated.
|
|
3685
|
+
voxel_size (float): Voxel size of the new volume.
|
|
3686
|
+
translation (array-like): Translation between the index and world spaces.
|
|
3687
|
+
device (Devicelike): The CUDA device to create the volume on, e.g.: "cuda" or "cuda:0".
|
|
3688
|
+
|
|
3689
|
+
"""
|
|
3690
|
+
device = warp.get_device(device)
|
|
3691
|
+
|
|
3692
|
+
if voxel_size <= 0.0:
|
|
3693
|
+
raise RuntimeError(f"Voxel size must be positive! Got {voxel_size}")
|
|
3694
|
+
if not device.is_cuda:
|
|
3695
|
+
raise RuntimeError("Only CUDA devices are supported for allocate_by_tiles")
|
|
3696
|
+
if not (is_array(voxel_points) and voxel_points.is_contiguous):
|
|
3697
|
+
raise RuntimeError("tile_points must be a contiguous array")
|
|
3698
|
+
if not _is_contiguous_vec_like_array(voxel_points, vec_length=3, scalar_types=(float32, int32)):
|
|
3699
|
+
raise RuntimeError(
|
|
3700
|
+
"voxel_points must be contiguous and either a 1D warp array of vec3f or vec3i or a 2D n-by-3 array of int32 or float32."
|
|
3701
|
+
)
|
|
3702
|
+
if not voxel_points.device.is_cuda:
|
|
3703
|
+
voxel_points = voxel_points.to(device)
|
|
3704
|
+
|
|
3705
|
+
volume = cls(data=None)
|
|
3706
|
+
volume.device = device
|
|
3707
|
+
in_world_space = type_scalar_type(voxel_points.dtype) == float32
|
|
3708
|
+
|
|
3709
|
+
volume.id = volume.runtime.core.volume_from_active_voxels_device(
|
|
3710
|
+
volume.device.context,
|
|
3711
|
+
ctypes.c_void_p(voxel_points.ptr),
|
|
3712
|
+
voxel_points.shape[0],
|
|
3713
|
+
voxel_size,
|
|
3714
|
+
translation[0],
|
|
3715
|
+
translation[1],
|
|
3716
|
+
translation[2],
|
|
3717
|
+
in_world_space,
|
|
3718
|
+
)
|
|
3719
|
+
|
|
3720
|
+
if volume.id == 0:
|
|
3721
|
+
raise RuntimeError("Failed to create volume")
|
|
3722
|
+
|
|
3723
|
+
return volume
|
|
3724
|
+
|
|
3725
|
+
|
|
3726
|
+
def _is_contiguous_vec_like_array(array, vec_length: int, scalar_types: Tuple[type]) -> bool:
|
|
3727
|
+
if not (is_array(array) and array.is_contiguous):
|
|
3728
|
+
return False
|
|
3729
|
+
if type_scalar_type(array.dtype) not in scalar_types:
|
|
3730
|
+
return False
|
|
3731
|
+
return (array.ndim == 1 and type_length(array.dtype) == vec_length) or (
|
|
3732
|
+
array.ndim == 2 and array.shape[1] == vec_length and type_length(array.dtype) == 1
|
|
3733
|
+
)
|
|
3734
|
+
|
|
3360
3735
|
|
|
3361
3736
|
# definition just for kernel type (cannot be a parameter), see mesh.h
|
|
3362
3737
|
# NOTE: its layout must match the corresponding struct defined in C.
|
|
@@ -4185,6 +4560,36 @@ class HashGrid:
|
|
|
4185
4560
|
|
|
4186
4561
|
class MarchingCubes:
|
|
4187
4562
|
def __init__(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int, device=None):
|
|
4563
|
+
"""CUDA-based Marching Cubes algorithm to extract a 2D surface mesh from a 3D volume.
|
|
4564
|
+
|
|
4565
|
+
Attributes:
|
|
4566
|
+
id: Unique identifier for this object.
|
|
4567
|
+
verts (:class:`warp.array`): Array of vertex positions of type :class:`warp.vec3f`
|
|
4568
|
+
for the output surface mesh.
|
|
4569
|
+
This is populated after running :func:`surface`.
|
|
4570
|
+
indices (:class:`warp.array`): Array containing indices of type :class:`warp.int32`
|
|
4571
|
+
defining triangles for the output surface mesh.
|
|
4572
|
+
This is populated after running :func:`surface`.
|
|
4573
|
+
|
|
4574
|
+
Each set of three consecutive integers in the array represents a single triangle,
|
|
4575
|
+
in which each integer is an index referring to a vertex in the :attr:`verts` array.
|
|
4576
|
+
|
|
4577
|
+
Args:
|
|
4578
|
+
nx: Number of cubes in the x-direction.
|
|
4579
|
+
ny: Number of cubes in the y-direction.
|
|
4580
|
+
nz: Number of cubes in the z-direction.
|
|
4581
|
+
max_verts: Maximum expected number of vertices (used for array preallocation).
|
|
4582
|
+
max_tris: Maximum expected number of triangles (used for array preallocation).
|
|
4583
|
+
device (Devicelike): CUDA device on which to run marching cubes and allocate memory.
|
|
4584
|
+
|
|
4585
|
+
Raises:
|
|
4586
|
+
RuntimeError: ``device`` not a CUDA device.
|
|
4587
|
+
|
|
4588
|
+
.. note::
|
|
4589
|
+
The shape of the marching cubes should match the shape of the scalar field being surfaced.
|
|
4590
|
+
|
|
4591
|
+
"""
|
|
4592
|
+
|
|
4188
4593
|
self.id = 0
|
|
4189
4594
|
|
|
4190
4595
|
self.runtime = warp.context.runtime
|
|
@@ -4210,7 +4615,7 @@ class MarchingCubes:
|
|
|
4210
4615
|
from warp.context import zeros
|
|
4211
4616
|
|
|
4212
4617
|
self.verts = zeros(max_verts, dtype=vec3, device=self.device)
|
|
4213
|
-
self.indices = zeros(max_tris * 3, dtype=
|
|
4618
|
+
self.indices = zeros(max_tris * 3, dtype=warp.int32, device=self.device)
|
|
4214
4619
|
|
|
4215
4620
|
# alloc surfacer
|
|
4216
4621
|
self.id = ctypes.c_uint64(self.alloc(self.device.context))
|
|
@@ -4224,7 +4629,19 @@ class MarchingCubes:
|
|
|
4224
4629
|
# destroy surfacer
|
|
4225
4630
|
self.free(self.id)
|
|
4226
4631
|
|
|
4227
|
-
def resize(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int):
|
|
4632
|
+
def resize(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int) -> None:
|
|
4633
|
+
"""Update the expected input and maximum output sizes for the marching cubes calculation.
|
|
4634
|
+
|
|
4635
|
+
This function has no immediate effect on the underlying buffers.
|
|
4636
|
+
The new values take effect on the next :func:`surface` call.
|
|
4637
|
+
|
|
4638
|
+
Args:
|
|
4639
|
+
nx: Number of cubes in the x-direction.
|
|
4640
|
+
ny: Number of cubes in the y-direction.
|
|
4641
|
+
nz: Number of cubes in the z-direction.
|
|
4642
|
+
max_verts: Maximum expected number of vertices (used for array preallocation).
|
|
4643
|
+
max_tris: Maximum expected number of triangles (used for array preallocation).
|
|
4644
|
+
"""
|
|
4228
4645
|
# actual allocations will be resized on next call to surface()
|
|
4229
4646
|
self.nx = nx
|
|
4230
4647
|
self.ny = ny
|
|
@@ -4232,13 +4649,37 @@ class MarchingCubes:
|
|
|
4232
4649
|
self.max_verts = max_verts
|
|
4233
4650
|
self.max_tris = max_tris
|
|
4234
4651
|
|
|
4235
|
-
def surface(self, field: array(dtype=float), threshold: float):
|
|
4652
|
+
def surface(self, field: array(dtype=float, ndim=3), threshold: float) -> None:
|
|
4653
|
+
"""Compute a 2D surface mesh of a given isosurface from a 3D scalar field.
|
|
4654
|
+
|
|
4655
|
+
The triangles and vertices defining the output mesh are written to the
|
|
4656
|
+
:attr:`indices` and :attr:`verts` arrays.
|
|
4657
|
+
|
|
4658
|
+
Args:
|
|
4659
|
+
field: Scalar field from which to generate a mesh.
|
|
4660
|
+
threshold: Target isosurface value.
|
|
4661
|
+
|
|
4662
|
+
Raises:
|
|
4663
|
+
ValueError: ``field`` is not a 3D array.
|
|
4664
|
+
ValueError: Marching cubes shape does not match the shape of ``field``.
|
|
4665
|
+
RuntimeError: :attr:`max_verts` and/or :attr:`max_tris` might be too small to hold the surface mesh.
|
|
4666
|
+
"""
|
|
4667
|
+
|
|
4236
4668
|
# WP_API int marching_cubes_surface_host(const float* field, int nx, int ny, int nz, float threshold, wp::vec3* verts, int* triangles, int max_verts, int max_tris, int* out_num_verts, int* out_num_tris);
|
|
4237
4669
|
num_verts = ctypes.c_int(0)
|
|
4238
4670
|
num_tris = ctypes.c_int(0)
|
|
4239
4671
|
|
|
4240
4672
|
self.runtime.core.marching_cubes_surface_device.restype = ctypes.c_int
|
|
4241
4673
|
|
|
4674
|
+
# For now we require that input field shape matches nx, ny, nz
|
|
4675
|
+
if field.ndim != 3:
|
|
4676
|
+
raise ValueError(f"Input field must be a three-dimensional array (got {field.ndim}).")
|
|
4677
|
+
if field.shape[0] != self.nx or field.shape[1] != self.ny or field.shape[2] != self.nz:
|
|
4678
|
+
raise ValueError(
|
|
4679
|
+
f"Marching cubes shape ({self.nx}, {self.ny}, {self.nz}) does not match the "
|
|
4680
|
+
f"input array shape {field.shape}."
|
|
4681
|
+
)
|
|
4682
|
+
|
|
4242
4683
|
error = self.runtime.core.marching_cubes_surface_device(
|
|
4243
4684
|
self.id,
|
|
4244
4685
|
ctypes.cast(field.ptr, ctypes.c_void_p),
|
|
@@ -4362,7 +4803,7 @@ def infer_argument_types(args, template_types, arg_names=None):
|
|
|
4362
4803
|
arg_types.append(arg_type(dtype=arg.dtype, ndim=arg.ndim))
|
|
4363
4804
|
elif arg_type in warp.types.scalar_and_bool_types:
|
|
4364
4805
|
arg_types.append(arg_type)
|
|
4365
|
-
elif arg_type in (int, float):
|
|
4806
|
+
elif arg_type in (int, float, builtins.bool):
|
|
4366
4807
|
# canonicalize type
|
|
4367
4808
|
arg_types.append(warp.types.type_to_warp(arg_type))
|
|
4368
4809
|
elif hasattr(arg_type, "_wp_scalar_type_"):
|