warp-lang 1.1.0__py3-none-win_amd64.whl → 1.2.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.
Potentially problematic release.
This version of warp-lang might be problematic. Click here for more details.
- warp/bin/warp-clang.dll +0 -0
- warp/bin/warp.dll +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 +418 -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 +514 -77
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.0.dist-info}/METADATA +57 -30
- warp_lang-1.2.0.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.0.dist-info}/LICENSE.md +0 -0
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.0.dist-info}/WHEEL +0 -0
- {warp_lang-1.1.0.dist-info → warp_lang-1.2.0.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:
|
|
@@ -1676,7 +1651,7 @@ class array(Array):
|
|
|
1676
1651
|
# Performance note: try first, ask questions later
|
|
1677
1652
|
device = warp.context.runtime.get_device(device)
|
|
1678
1653
|
except:
|
|
1679
|
-
warp.context.
|
|
1654
|
+
warp.context.init()
|
|
1680
1655
|
raise
|
|
1681
1656
|
|
|
1682
1657
|
if device.is_cuda:
|
|
@@ -1805,7 +1780,7 @@ class array(Array):
|
|
|
1805
1780
|
# Performance note: try first, ask questions later
|
|
1806
1781
|
device = warp.context.runtime.get_device(device)
|
|
1807
1782
|
except:
|
|
1808
|
-
warp.context.
|
|
1783
|
+
warp.context.init()
|
|
1809
1784
|
raise
|
|
1810
1785
|
|
|
1811
1786
|
if device.is_cpu and not copy and not pinned:
|
|
@@ -1831,7 +1806,7 @@ class array(Array):
|
|
|
1831
1806
|
# Performance note: try first, ask questions later
|
|
1832
1807
|
device = warp.context.runtime.get_device(device)
|
|
1833
1808
|
except:
|
|
1834
|
-
warp.context.
|
|
1809
|
+
warp.context.init()
|
|
1835
1810
|
raise
|
|
1836
1811
|
|
|
1837
1812
|
check_array_shape(shape)
|
|
@@ -1878,7 +1853,7 @@ class array(Array):
|
|
|
1878
1853
|
# Performance note: try first, ask questions later
|
|
1879
1854
|
device = warp.context.runtime.get_device(device)
|
|
1880
1855
|
except:
|
|
1881
|
-
warp.context.
|
|
1856
|
+
warp.context.init()
|
|
1882
1857
|
raise
|
|
1883
1858
|
|
|
1884
1859
|
check_array_shape(shape)
|
|
@@ -3022,11 +2997,12 @@ class Volume:
|
|
|
3022
2997
|
#: Enum value to specify trilinear interpolation during sampling
|
|
3023
2998
|
LINEAR = constant(1)
|
|
3024
2999
|
|
|
3025
|
-
def __init__(self, data: array):
|
|
3000
|
+
def __init__(self, data: array, copy: bool = True):
|
|
3026
3001
|
"""Class representing a sparse grid.
|
|
3027
3002
|
|
|
3028
3003
|
Args:
|
|
3029
3004
|
data (:class:`warp.array`): Array of bytes representing the volume in NanoVDB format
|
|
3005
|
+
copy (bool): Whether the incoming data will be copied or aliased
|
|
3030
3006
|
"""
|
|
3031
3007
|
|
|
3032
3008
|
self.id = 0
|
|
@@ -3036,16 +3012,16 @@ class Volume:
|
|
|
3036
3012
|
|
|
3037
3013
|
if data is None:
|
|
3038
3014
|
return
|
|
3039
|
-
|
|
3040
|
-
if data.device is None:
|
|
3041
|
-
raise RuntimeError("Invalid device")
|
|
3042
3015
|
self.device = data.device
|
|
3043
3016
|
|
|
3017
|
+
owner = False
|
|
3044
3018
|
if self.device.is_cpu:
|
|
3045
|
-
self.id = self.runtime.core.volume_create_host(
|
|
3019
|
+
self.id = self.runtime.core.volume_create_host(
|
|
3020
|
+
ctypes.cast(data.ptr, ctypes.c_void_p), data.size, copy, owner
|
|
3021
|
+
)
|
|
3046
3022
|
else:
|
|
3047
3023
|
self.id = self.runtime.core.volume_create_device(
|
|
3048
|
-
self.device.context, ctypes.cast(data.ptr, ctypes.c_void_p), data.size
|
|
3024
|
+
self.device.context, ctypes.cast(data.ptr, ctypes.c_void_p), data.size, copy, owner
|
|
3049
3025
|
)
|
|
3050
3026
|
|
|
3051
3027
|
if self.id == 0:
|
|
@@ -3066,32 +3042,90 @@ class Volume:
|
|
|
3066
3042
|
"""Returns the raw memory buffer of the Volume as an array"""
|
|
3067
3043
|
buf = ctypes.c_void_p(0)
|
|
3068
3044
|
size = ctypes.c_uint64(0)
|
|
3045
|
+
self.runtime.core.volume_get_buffer_info(self.id, ctypes.byref(buf), ctypes.byref(size))
|
|
3046
|
+
return array(ptr=buf.value, dtype=uint8, shape=size.value, device=self.device, owner=False)
|
|
3047
|
+
|
|
3048
|
+
def get_tile_count(self) -> int:
|
|
3049
|
+
"""Returns the number of tiles (NanoVDB leaf nodes) of the volume"""
|
|
3050
|
+
|
|
3051
|
+
voxel_count, tile_count = (
|
|
3052
|
+
ctypes.c_uint64(0),
|
|
3053
|
+
ctypes.c_uint32(0),
|
|
3054
|
+
)
|
|
3055
|
+
self.runtime.core.volume_get_tile_and_voxel_count(self.id, ctypes.byref(tile_count), ctypes.byref(voxel_count))
|
|
3056
|
+
return tile_count.value
|
|
3057
|
+
|
|
3058
|
+
def get_tiles(self, out: Optional[array] = None) -> array:
|
|
3059
|
+
"""Returns the integer coordinates of all allocated tiles for this volume.
|
|
3060
|
+
|
|
3061
|
+
Args:
|
|
3062
|
+
out (:class:`warp.array`, optional): If provided, use the `out` array to store the tile coordinates, otherwise
|
|
3063
|
+
a new array will be allocated. `out` must be a contiguous array of ``tile_count`` ``vec3i`` or ``tile_count x 3`` ``int32``
|
|
3064
|
+
on the same device as this volume.
|
|
3065
|
+
"""
|
|
3066
|
+
|
|
3067
|
+
if self.id == 0:
|
|
3068
|
+
raise RuntimeError("Invalid Volume")
|
|
3069
|
+
|
|
3070
|
+
tile_count = self.get_tile_count()
|
|
3071
|
+
if out is None:
|
|
3072
|
+
out = warp.empty(dtype=int32, shape=(tile_count, 3), device=self.device)
|
|
3073
|
+
elif out.device != self.device or out.shape[0] < tile_count:
|
|
3074
|
+
raise RuntimeError(f"'out' array must an array with at least {tile_count} rows on device {self.device}")
|
|
3075
|
+
elif not _is_contiguous_vec_like_array(out, vec_length=3, scalar_types=(int32,)):
|
|
3076
|
+
raise RuntimeError(
|
|
3077
|
+
"'out' must be a contiguous 1D array with type vec3i or a 2D array of type int32 with shape (N, 3) "
|
|
3078
|
+
)
|
|
3079
|
+
|
|
3069
3080
|
if self.device.is_cpu:
|
|
3070
|
-
self.runtime.core.
|
|
3081
|
+
self.runtime.core.volume_get_tiles_host(self.id, out.ptr)
|
|
3071
3082
|
else:
|
|
3072
|
-
self.runtime.core.
|
|
3073
|
-
|
|
3083
|
+
self.runtime.core.volume_get_tiles_device(self.id, out.ptr)
|
|
3084
|
+
|
|
3085
|
+
return out
|
|
3086
|
+
|
|
3087
|
+
def get_voxel_count(self) -> int:
|
|
3088
|
+
"""Returns the total number of allocated voxels for this volume"""
|
|
3089
|
+
|
|
3090
|
+
voxel_count, tile_count = (
|
|
3091
|
+
ctypes.c_uint64(0),
|
|
3092
|
+
ctypes.c_uint32(0),
|
|
3093
|
+
)
|
|
3094
|
+
self.runtime.core.volume_get_tile_and_voxel_count(self.id, ctypes.byref(tile_count), ctypes.byref(voxel_count))
|
|
3095
|
+
return voxel_count.value
|
|
3096
|
+
|
|
3097
|
+
def get_voxels(self, out: Optional[array] = None) -> array:
|
|
3098
|
+
"""Returns the integer coordinates of all allocated voxels for this volume.
|
|
3099
|
+
|
|
3100
|
+
Args:
|
|
3101
|
+
out (:class:`warp.array`, optional): If provided, use the `out` array to store the voxel coordinates, otherwise
|
|
3102
|
+
a new array will be allocated. `out` must be a contiguous array of ``voxel_count`` ``vec3i`` or ``voxel_count x 3`` ``int32``
|
|
3103
|
+
on the same device as this volume.
|
|
3104
|
+
"""
|
|
3074
3105
|
|
|
3075
|
-
def get_tiles(self) -> array:
|
|
3076
3106
|
if self.id == 0:
|
|
3077
3107
|
raise RuntimeError("Invalid Volume")
|
|
3078
3108
|
|
|
3079
|
-
|
|
3080
|
-
|
|
3109
|
+
voxel_count = self.get_voxel_count()
|
|
3110
|
+
if out is None:
|
|
3111
|
+
out = warp.empty(dtype=int32, shape=(voxel_count, 3), device=self.device)
|
|
3112
|
+
elif out.device != self.device or out.shape[0] < voxel_count:
|
|
3113
|
+
raise RuntimeError(f"'out' array must an array with at least {voxel_count} rows on device {self.device}")
|
|
3114
|
+
elif not _is_contiguous_vec_like_array(out, vec_length=3, scalar_types=(int32,)):
|
|
3115
|
+
raise RuntimeError(
|
|
3116
|
+
"'out' must be a contiguous 1D array with type vec3i or a 2D array of type int32 with shape (N, 3) "
|
|
3117
|
+
)
|
|
3118
|
+
|
|
3081
3119
|
if self.device.is_cpu:
|
|
3082
|
-
self.runtime.core.
|
|
3083
|
-
deleter = self.device.default_allocator.deleter
|
|
3120
|
+
self.runtime.core.volume_get_voxels_host(self.id, out.ptr)
|
|
3084
3121
|
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)
|
|
3122
|
+
self.runtime.core.volume_get_voxels_device(self.id, out.ptr)
|
|
3091
3123
|
|
|
3092
|
-
return
|
|
3124
|
+
return out
|
|
3093
3125
|
|
|
3094
3126
|
def get_voxel_size(self) -> Tuple[float, float, float]:
|
|
3127
|
+
"""Voxel size, i.e, world coordinates of voxel's diagonal vector"""
|
|
3128
|
+
|
|
3095
3129
|
if self.id == 0:
|
|
3096
3130
|
raise RuntimeError("Invalid Volume")
|
|
3097
3131
|
|
|
@@ -3099,9 +3133,181 @@ class Volume:
|
|
|
3099
3133
|
self.runtime.core.volume_get_voxel_size(self.id, ctypes.byref(dx), ctypes.byref(dy), ctypes.byref(dz))
|
|
3100
3134
|
return (dx.value, dy.value, dz.value)
|
|
3101
3135
|
|
|
3136
|
+
class GridInfo(NamedTuple):
|
|
3137
|
+
"""Grid metadata"""
|
|
3138
|
+
|
|
3139
|
+
name: str
|
|
3140
|
+
"""Grid name"""
|
|
3141
|
+
size_in_bytes: int
|
|
3142
|
+
"""Size of this grid's data, in bytes"""
|
|
3143
|
+
|
|
3144
|
+
grid_index: int
|
|
3145
|
+
"""Index of this grid in the data buffer"""
|
|
3146
|
+
grid_count: int
|
|
3147
|
+
"""Total number of grids in the data buffer"""
|
|
3148
|
+
type_str: str
|
|
3149
|
+
"""String describing the type of the grid values"""
|
|
3150
|
+
|
|
3151
|
+
translation: vec3f
|
|
3152
|
+
"""Index-to-world translation"""
|
|
3153
|
+
transform_matrix: mat33f
|
|
3154
|
+
"""Linear part of the index-to-world transform"""
|
|
3155
|
+
|
|
3156
|
+
def get_grid_info(self) -> Volume.GridInfo:
|
|
3157
|
+
"""Returns the metadata associated with this Volume"""
|
|
3158
|
+
|
|
3159
|
+
grid_index = ctypes.c_uint32(0)
|
|
3160
|
+
grid_count = ctypes.c_uint32(0)
|
|
3161
|
+
grid_size = ctypes.c_uint64(0)
|
|
3162
|
+
translation_buffer = (ctypes.c_float * 3)()
|
|
3163
|
+
transform_buffer = (ctypes.c_float * 9)()
|
|
3164
|
+
type_str_buffer = (ctypes.c_char * 16)()
|
|
3165
|
+
|
|
3166
|
+
name = self.runtime.core.volume_get_grid_info(
|
|
3167
|
+
self.id,
|
|
3168
|
+
ctypes.byref(grid_size),
|
|
3169
|
+
ctypes.byref(grid_index),
|
|
3170
|
+
ctypes.byref(grid_count),
|
|
3171
|
+
translation_buffer,
|
|
3172
|
+
transform_buffer,
|
|
3173
|
+
type_str_buffer,
|
|
3174
|
+
)
|
|
3175
|
+
|
|
3176
|
+
if name is None:
|
|
3177
|
+
raise RuntimeError("Invalid volume")
|
|
3178
|
+
|
|
3179
|
+
return Volume.GridInfo(
|
|
3180
|
+
name.decode("ascii"),
|
|
3181
|
+
grid_size.value,
|
|
3182
|
+
grid_index.value,
|
|
3183
|
+
grid_count.value,
|
|
3184
|
+
type_str_buffer.value.decode("ascii"),
|
|
3185
|
+
vec3f.from_buffer_copy(translation_buffer),
|
|
3186
|
+
mat33f.from_buffer_copy(transform_buffer),
|
|
3187
|
+
)
|
|
3188
|
+
|
|
3189
|
+
_nvdb_type_to_dtype = {
|
|
3190
|
+
"float": float32,
|
|
3191
|
+
"double": float64,
|
|
3192
|
+
"int16": int16,
|
|
3193
|
+
"int32": int32,
|
|
3194
|
+
"int64": int64,
|
|
3195
|
+
"Vec3f": vec3f,
|
|
3196
|
+
"Vec3d": vec3d,
|
|
3197
|
+
"Half": float16,
|
|
3198
|
+
"uint32": uint32,
|
|
3199
|
+
"bool": bool,
|
|
3200
|
+
"Vec4f": vec4f,
|
|
3201
|
+
"Vec4d": vec4d,
|
|
3202
|
+
"Vec3u8": vec3ub,
|
|
3203
|
+
"Vec3u16": vec3us,
|
|
3204
|
+
"uint8": uint8,
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
@property
|
|
3208
|
+
def dtype(self) -> type:
|
|
3209
|
+
"""Type of the Volume's values as a Warp type.
|
|
3210
|
+
|
|
3211
|
+
If the grid does not contain values (e.g. index grids) or if the NanoVDB type is not
|
|
3212
|
+
representable as a Warp type, returns ``None``.
|
|
3213
|
+
"""
|
|
3214
|
+
return Volume._nvdb_type_to_dtype.get(self.get_grid_info().type_str, None)
|
|
3215
|
+
|
|
3216
|
+
_nvdb_index_types = ("Index", "OnIndex", "IndexMask", "OnIndexMask")
|
|
3217
|
+
|
|
3218
|
+
@property
|
|
3219
|
+
def is_index(self) -> bool:
|
|
3220
|
+
"""Whether this Volume contains an index grid, that is, a type of grid that does
|
|
3221
|
+
not explicitly store values but associates each voxel to linearized index.
|
|
3222
|
+
"""
|
|
3223
|
+
|
|
3224
|
+
return self.get_grid_info().type_str in Volume._nvdb_index_types
|
|
3225
|
+
|
|
3226
|
+
def get_feature_array_count(self) -> int:
|
|
3227
|
+
"""Returns the number of supplemental data arrays stored alongside the grid"""
|
|
3228
|
+
|
|
3229
|
+
return self.runtime.core.volume_get_blind_data_count(self.id)
|
|
3230
|
+
|
|
3231
|
+
class FeatureArrayInfo(NamedTuple):
|
|
3232
|
+
"""Metadata for a supplemental data array"""
|
|
3233
|
+
|
|
3234
|
+
name: str
|
|
3235
|
+
"""Name of the data array"""
|
|
3236
|
+
ptr: int
|
|
3237
|
+
"""Memory address of the start of the array"""
|
|
3238
|
+
|
|
3239
|
+
value_size: int
|
|
3240
|
+
"""Size in bytes of the array values"""
|
|
3241
|
+
value_count: int
|
|
3242
|
+
"""Number of values in the array"""
|
|
3243
|
+
type_str: str
|
|
3244
|
+
"""String describing the type of the array values"""
|
|
3245
|
+
|
|
3246
|
+
def get_feature_array_info(self, feature_index: int) -> Volume.FeatureArrayInfo:
|
|
3247
|
+
"""Returns the metadata associated to the feature array at `feature_index`"""
|
|
3248
|
+
|
|
3249
|
+
buf = ctypes.c_void_p(0)
|
|
3250
|
+
value_count = ctypes.c_uint64(0)
|
|
3251
|
+
value_size = ctypes.c_uint32(0)
|
|
3252
|
+
type_str_buffer = (ctypes.c_char * 16)()
|
|
3253
|
+
|
|
3254
|
+
name = self.runtime.core.volume_get_blind_data_info(
|
|
3255
|
+
self.id,
|
|
3256
|
+
feature_index,
|
|
3257
|
+
ctypes.byref(buf),
|
|
3258
|
+
ctypes.byref(value_count),
|
|
3259
|
+
ctypes.byref(value_size),
|
|
3260
|
+
type_str_buffer,
|
|
3261
|
+
)
|
|
3262
|
+
|
|
3263
|
+
if buf.value is None:
|
|
3264
|
+
raise RuntimeError("Invalid feature array")
|
|
3265
|
+
|
|
3266
|
+
return Volume.FeatureArrayInfo(
|
|
3267
|
+
name.decode("ascii"),
|
|
3268
|
+
buf.value,
|
|
3269
|
+
value_size.value,
|
|
3270
|
+
value_count.value,
|
|
3271
|
+
type_str_buffer.value.decode("ascii"),
|
|
3272
|
+
)
|
|
3273
|
+
|
|
3274
|
+
def feature_array(self, feature_index: int, dtype=None) -> array:
|
|
3275
|
+
"""Returns one the the grid's feature data arrays as a Warp array
|
|
3276
|
+
|
|
3277
|
+
Args:
|
|
3278
|
+
feature_index: Index of the supplemental data array in the grid
|
|
3279
|
+
dtype: Type for the returned Warp array. If not provided, will be deduced from the array metadata.
|
|
3280
|
+
"""
|
|
3281
|
+
|
|
3282
|
+
info = self.get_feature_array_info(feature_index)
|
|
3283
|
+
|
|
3284
|
+
if dtype is None:
|
|
3285
|
+
try:
|
|
3286
|
+
dtype = Volume._nvdb_type_to_dtype[info.type_str]
|
|
3287
|
+
except KeyError:
|
|
3288
|
+
# Unknown type, default to byte array
|
|
3289
|
+
dtype = uint8
|
|
3290
|
+
|
|
3291
|
+
value_count = info.value_count
|
|
3292
|
+
value_size = info.value_size
|
|
3293
|
+
|
|
3294
|
+
if type_size_in_bytes(dtype) == 1:
|
|
3295
|
+
# allow requesting a byte array from any type
|
|
3296
|
+
value_count *= value_size
|
|
3297
|
+
value_size = 1
|
|
3298
|
+
elif value_size == 1 and (value_count % type_size_in_bytes(dtype)) == 0:
|
|
3299
|
+
# allow converting a byte array to any type
|
|
3300
|
+
value_size = type_size_in_bytes(dtype)
|
|
3301
|
+
value_count = value_count // value_size
|
|
3302
|
+
|
|
3303
|
+
if type_size_in_bytes(dtype) != value_size:
|
|
3304
|
+
raise RuntimeError(f"Cannot cast feature data of size {value_size} to array dtype {type_repr(dtype)}")
|
|
3305
|
+
|
|
3306
|
+
return array(ptr=info.ptr, dtype=dtype, shape=value_count, device=self.device, owner=False)
|
|
3307
|
+
|
|
3102
3308
|
@classmethod
|
|
3103
3309
|
def load_from_nvdb(cls, file_or_buffer, device=None) -> Volume:
|
|
3104
|
-
"""Creates a Volume object from a NanoVDB file or in-memory buffer.
|
|
3310
|
+
"""Creates a Volume object from a serialized NanoVDB file or in-memory buffer.
|
|
3105
3311
|
|
|
3106
3312
|
Returns:
|
|
3107
3313
|
|
|
@@ -3113,28 +3319,117 @@ class Volume:
|
|
|
3113
3319
|
data = file_or_buffer
|
|
3114
3320
|
|
|
3115
3321
|
magic, version, grid_count, codec = struct.unpack("<QIHH", data[0:16])
|
|
3116
|
-
if magic
|
|
3322
|
+
if magic not in (0x304244566F6E614E, 0x324244566F6E614E): # NanoVDB0 or NanoVDB2 in hex, little-endian
|
|
3117
3323
|
raise RuntimeError("NanoVDB signature not found")
|
|
3118
3324
|
if version >> 21 != 32: # checking major version
|
|
3119
3325
|
raise RuntimeError("Unsupported NanoVDB version")
|
|
3120
|
-
if grid_count != 1:
|
|
3121
|
-
raise RuntimeError("Only NVDBs with exactly one grid are supported")
|
|
3122
3326
|
|
|
3123
|
-
|
|
3327
|
+
# Skip over segment metadata, store total payload size
|
|
3328
|
+
grid_data_offset = 16 # sizeof(FileHeader)
|
|
3329
|
+
tot_file_size = 0
|
|
3330
|
+
for _ in range(grid_count):
|
|
3331
|
+
grid_file_size = struct.unpack("<Q", data[grid_data_offset + 8 : grid_data_offset + 16])[0]
|
|
3332
|
+
tot_file_size += grid_file_size
|
|
3333
|
+
|
|
3334
|
+
grid_name_size = struct.unpack("<I", data[grid_data_offset + 136 : grid_data_offset + 140])[0]
|
|
3335
|
+
grid_data_offset += 176 + grid_name_size # sizeof(FileMetadata) + grid name
|
|
3336
|
+
|
|
3337
|
+
file_end = grid_data_offset + tot_file_size
|
|
3338
|
+
|
|
3124
3339
|
if codec == 0: # no compression
|
|
3125
|
-
grid_data = data[grid_data_offset:]
|
|
3340
|
+
grid_data = data[grid_data_offset:file_end]
|
|
3126
3341
|
elif codec == 1: # zip compression
|
|
3127
|
-
grid_data =
|
|
3342
|
+
grid_data = bytearray()
|
|
3343
|
+
while grid_data_offset < file_end:
|
|
3344
|
+
chunk_size = struct.unpack("<Q", data[grid_data_offset : grid_data_offset + 8])[0]
|
|
3345
|
+
grid_data += zlib.decompress(data[grid_data_offset + 8 :])
|
|
3346
|
+
grid_data_offset += 8 + chunk_size
|
|
3347
|
+
|
|
3348
|
+
elif codec == 2: # blosc compression
|
|
3349
|
+
try:
|
|
3350
|
+
import blosc
|
|
3351
|
+
except ImportError as err:
|
|
3352
|
+
raise RuntimeError(
|
|
3353
|
+
f"NanoVDB buffer is compressed using blosc, but Python module could not be imported: {err}"
|
|
3354
|
+
) from err
|
|
3355
|
+
|
|
3356
|
+
grid_data = bytearray()
|
|
3357
|
+
while grid_data_offset < file_end:
|
|
3358
|
+
chunk_size = struct.unpack("<Q", data[grid_data_offset : grid_data_offset + 8])[0]
|
|
3359
|
+
grid_data += blosc.decompress(data[grid_data_offset + 8 :])
|
|
3360
|
+
grid_data_offset += 8 + chunk_size
|
|
3128
3361
|
else:
|
|
3129
3362
|
raise RuntimeError(f"Unsupported codec code: {codec}")
|
|
3130
3363
|
|
|
3131
3364
|
magic = struct.unpack("<Q", grid_data[0:8])[0]
|
|
3132
|
-
if magic
|
|
3365
|
+
if magic not in (0x304244566F6E614E, 0x314244566F6E614E): # NanoVDB0 or NanoVDB1 in hex, little-endian
|
|
3133
3366
|
raise RuntimeError("NanoVDB signature not found on grid!")
|
|
3134
3367
|
|
|
3135
3368
|
data_array = array(np.frombuffer(grid_data, dtype=np.byte), device=device)
|
|
3136
3369
|
return cls(data_array)
|
|
3137
3370
|
|
|
3371
|
+
@classmethod
|
|
3372
|
+
def load_from_address(cls, grid_ptr: int, buffer_size: int = 0, device=None) -> Volume:
|
|
3373
|
+
"""
|
|
3374
|
+
Creates a new :class:`Volume` aliasing an in-memory grid buffer.
|
|
3375
|
+
|
|
3376
|
+
In contrast to :meth:`load_from_nvdb` which should be used to load serialized NanoVDB grids,
|
|
3377
|
+
here the buffer must be uncompressed and must not contain file header information.
|
|
3378
|
+
If the passed address does not contain a NanoVDB grid, the behavior of this function is undefined.
|
|
3379
|
+
|
|
3380
|
+
Args:
|
|
3381
|
+
grid_ptr: Integer address of the start of the grid buffer
|
|
3382
|
+
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`.
|
|
3383
|
+
device: Device of the buffer, and of the returned Volume. If not provided, the current Warp device is assumed.
|
|
3384
|
+
|
|
3385
|
+
Returns the newly created Volume.
|
|
3386
|
+
"""
|
|
3387
|
+
|
|
3388
|
+
if not grid_ptr:
|
|
3389
|
+
raise (RuntimeError, "Invalid grid buffer pointer")
|
|
3390
|
+
|
|
3391
|
+
# Check that a Volume has not already been created for this address
|
|
3392
|
+
# (to allow this we would need to ref-count the volume descriptor)
|
|
3393
|
+
existing_buf = ctypes.c_void_p(0)
|
|
3394
|
+
existing_size = ctypes.c_uint64(0)
|
|
3395
|
+
warp.context.runtime.core.volume_get_buffer_info(
|
|
3396
|
+
grid_ptr, ctypes.byref(existing_buf), ctypes.byref(existing_size)
|
|
3397
|
+
)
|
|
3398
|
+
|
|
3399
|
+
if existing_buf.value is not None:
|
|
3400
|
+
raise RuntimeError(
|
|
3401
|
+
"A warp Volume has already been created for this grid, aliasing it more than once is not possible."
|
|
3402
|
+
)
|
|
3403
|
+
|
|
3404
|
+
data_array = array(ptr=grid_ptr, dtype=uint8, shape=buffer_size, owner=False, device=device)
|
|
3405
|
+
|
|
3406
|
+
return cls(data_array, copy=False)
|
|
3407
|
+
|
|
3408
|
+
def load_next_grid(self) -> Volume:
|
|
3409
|
+
"""
|
|
3410
|
+
Tries to create a new warp Volume for the next grid that is linked to by this Volume.
|
|
3411
|
+
|
|
3412
|
+
The existence of a next grid is deduced from the `grid_index` and `grid_count` metadata
|
|
3413
|
+
as well as the size of this Volume's in-memory buffer.
|
|
3414
|
+
|
|
3415
|
+
Returns the newly created Volume, or None if there is no next grid.
|
|
3416
|
+
"""
|
|
3417
|
+
|
|
3418
|
+
grid = self.get_grid_info()
|
|
3419
|
+
|
|
3420
|
+
array = self.array()
|
|
3421
|
+
|
|
3422
|
+
if grid.grid_index + 1 >= grid.grid_count or array.capacity <= grid.size_in_bytes:
|
|
3423
|
+
return None
|
|
3424
|
+
|
|
3425
|
+
next_volume = Volume.load_from_address(
|
|
3426
|
+
array.ptr + grid.size_in_bytes, buffer_size=array.capacity - grid.size_in_bytes, device=self.device
|
|
3427
|
+
)
|
|
3428
|
+
# makes the new Volume keep a reference to the current grid, as we're aliasing its buffer
|
|
3429
|
+
next_volume._previous_grid = self
|
|
3430
|
+
|
|
3431
|
+
return next_volume
|
|
3432
|
+
|
|
3138
3433
|
@classmethod
|
|
3139
3434
|
def load_from_numpy(
|
|
3140
3435
|
cls, ndarray: np.array, min_world=(0.0, 0.0, 0.0), voxel_size=1.0, bg_value=0.0, device=None
|
|
@@ -3286,11 +3581,11 @@ class Volume:
|
|
|
3286
3581
|
|
|
3287
3582
|
Args:
|
|
3288
3583
|
tile_points (:class:`warp.array`): Array of positions that define the tiles to be allocated.
|
|
3289
|
-
The array
|
|
3290
|
-
or
|
|
3584
|
+
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,
|
|
3585
|
+
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
3586
|
Repeated points per tile are allowed and will be efficiently deduplicated.
|
|
3292
3587
|
voxel_size (float): Voxel size of the new volume.
|
|
3293
|
-
bg_value (float or
|
|
3588
|
+
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
3589
|
translation (array-like): Translation between the index and world spaces.
|
|
3295
3590
|
device (Devicelike): The CUDA device to create the volume on, e.g.: "cuda" or "cuda:0".
|
|
3296
3591
|
|
|
@@ -3301,19 +3596,28 @@ class Volume:
|
|
|
3301
3596
|
raise RuntimeError(f"Voxel size must be positive! Got {voxel_size}")
|
|
3302
3597
|
if not device.is_cuda:
|
|
3303
3598
|
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!")
|
|
3599
|
+
if not _is_contiguous_vec_like_array(tile_points, vec_length=3, scalar_types=(float32, int32)):
|
|
3600
|
+
raise RuntimeError(
|
|
3601
|
+
"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."
|
|
3602
|
+
)
|
|
3310
3603
|
if not tile_points.device.is_cuda:
|
|
3311
|
-
tile_points =
|
|
3604
|
+
tile_points = tile_points.to(device)
|
|
3312
3605
|
|
|
3313
3606
|
volume = cls(data=None)
|
|
3314
3607
|
volume.device = device
|
|
3315
|
-
in_world_space = tile_points.dtype ==
|
|
3316
|
-
if
|
|
3608
|
+
in_world_space = type_scalar_type(tile_points.dtype) == float32
|
|
3609
|
+
if bg_value is None:
|
|
3610
|
+
volume.id = volume.runtime.core.volume_index_from_tiles_device(
|
|
3611
|
+
volume.device.context,
|
|
3612
|
+
ctypes.c_void_p(tile_points.ptr),
|
|
3613
|
+
tile_points.shape[0],
|
|
3614
|
+
voxel_size,
|
|
3615
|
+
translation[0],
|
|
3616
|
+
translation[1],
|
|
3617
|
+
translation[2],
|
|
3618
|
+
in_world_space,
|
|
3619
|
+
)
|
|
3620
|
+
elif hasattr(bg_value, "__len__"):
|
|
3317
3621
|
volume.id = volume.runtime.core.volume_v_from_tiles_device(
|
|
3318
3622
|
volume.device.context,
|
|
3319
3623
|
ctypes.c_void_p(tile_points.ptr),
|
|
@@ -3357,6 +3661,73 @@ class Volume:
|
|
|
3357
3661
|
|
|
3358
3662
|
return volume
|
|
3359
3663
|
|
|
3664
|
+
@classmethod
|
|
3665
|
+
def allocate_by_voxels(
|
|
3666
|
+
cls, voxel_points: array, voxel_size: float, translation=(0.0, 0.0, 0.0), device=None
|
|
3667
|
+
) -> Volume:
|
|
3668
|
+
"""Allocate a new Volume with active voxel for each point voxel_points.
|
|
3669
|
+
|
|
3670
|
+
This function creates an *index* Volume, a special kind of volume that does not any store any
|
|
3671
|
+
explicit payload but encodes a linearized index for each active voxel, allowing to lookup and
|
|
3672
|
+
sample data from arbitrary external arrays.
|
|
3673
|
+
|
|
3674
|
+
This function is only supported for CUDA devices.
|
|
3675
|
+
|
|
3676
|
+
Args:
|
|
3677
|
+
voxel_points (:class:`warp.array`): Array of positions that define the voxels to be allocated.
|
|
3678
|
+
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,
|
|
3679
|
+
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.
|
|
3680
|
+
Repeated points per tile are allowed and will be efficiently deduplicated.
|
|
3681
|
+
voxel_size (float): Voxel size of the new volume.
|
|
3682
|
+
translation (array-like): Translation between the index and world spaces.
|
|
3683
|
+
device (Devicelike): The CUDA device to create the volume on, e.g.: "cuda" or "cuda:0".
|
|
3684
|
+
|
|
3685
|
+
"""
|
|
3686
|
+
device = warp.get_device(device)
|
|
3687
|
+
|
|
3688
|
+
if voxel_size <= 0.0:
|
|
3689
|
+
raise RuntimeError(f"Voxel size must be positive! Got {voxel_size}")
|
|
3690
|
+
if not device.is_cuda:
|
|
3691
|
+
raise RuntimeError("Only CUDA devices are supported for allocate_by_tiles")
|
|
3692
|
+
if not (is_array(voxel_points) and voxel_points.is_contiguous):
|
|
3693
|
+
raise RuntimeError("tile_points must be a contiguous array")
|
|
3694
|
+
if not _is_contiguous_vec_like_array(voxel_points, vec_length=3, scalar_types=(float32, int32)):
|
|
3695
|
+
raise RuntimeError(
|
|
3696
|
+
"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."
|
|
3697
|
+
)
|
|
3698
|
+
if not voxel_points.device.is_cuda:
|
|
3699
|
+
voxel_points = voxel_points.to(device)
|
|
3700
|
+
|
|
3701
|
+
volume = cls(data=None)
|
|
3702
|
+
volume.device = device
|
|
3703
|
+
in_world_space = type_scalar_type(voxel_points.dtype) == float32
|
|
3704
|
+
|
|
3705
|
+
volume.id = volume.runtime.core.volume_from_active_voxels_device(
|
|
3706
|
+
volume.device.context,
|
|
3707
|
+
ctypes.c_void_p(voxel_points.ptr),
|
|
3708
|
+
voxel_points.shape[0],
|
|
3709
|
+
voxel_size,
|
|
3710
|
+
translation[0],
|
|
3711
|
+
translation[1],
|
|
3712
|
+
translation[2],
|
|
3713
|
+
in_world_space,
|
|
3714
|
+
)
|
|
3715
|
+
|
|
3716
|
+
if volume.id == 0:
|
|
3717
|
+
raise RuntimeError("Failed to create volume")
|
|
3718
|
+
|
|
3719
|
+
return volume
|
|
3720
|
+
|
|
3721
|
+
|
|
3722
|
+
def _is_contiguous_vec_like_array(array, vec_length: int, scalar_types: Tuple[type]) -> bool:
|
|
3723
|
+
if not (is_array(array) and array.is_contiguous):
|
|
3724
|
+
return False
|
|
3725
|
+
if type_scalar_type(array.dtype) not in scalar_types:
|
|
3726
|
+
return False
|
|
3727
|
+
return (array.ndim == 1 and type_length(array.dtype) == vec_length) or (
|
|
3728
|
+
array.ndim == 2 and array.shape[1] == vec_length and type_length(array.dtype) == 1
|
|
3729
|
+
)
|
|
3730
|
+
|
|
3360
3731
|
|
|
3361
3732
|
# definition just for kernel type (cannot be a parameter), see mesh.h
|
|
3362
3733
|
# NOTE: its layout must match the corresponding struct defined in C.
|
|
@@ -4185,6 +4556,36 @@ class HashGrid:
|
|
|
4185
4556
|
|
|
4186
4557
|
class MarchingCubes:
|
|
4187
4558
|
def __init__(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int, device=None):
|
|
4559
|
+
"""CUDA-based Marching Cubes algorithm to extract a 2D surface mesh from a 3D volume.
|
|
4560
|
+
|
|
4561
|
+
Attributes:
|
|
4562
|
+
id: Unique identifier for this object.
|
|
4563
|
+
verts (:class:`warp.array`): Array of vertex positions of type :class:`warp.vec3f`
|
|
4564
|
+
for the output surface mesh.
|
|
4565
|
+
This is populated after running :func:`surface`.
|
|
4566
|
+
indices (:class:`warp.array`): Array containing indices of type :class:`warp.int32`
|
|
4567
|
+
defining triangles for the output surface mesh.
|
|
4568
|
+
This is populated after running :func:`surface`.
|
|
4569
|
+
|
|
4570
|
+
Each set of three consecutive integers in the array represents a single triangle,
|
|
4571
|
+
in which each integer is an index referring to a vertex in the :attr:`verts` array.
|
|
4572
|
+
|
|
4573
|
+
Args:
|
|
4574
|
+
nx: Number of cubes in the x-direction.
|
|
4575
|
+
ny: Number of cubes in the y-direction.
|
|
4576
|
+
nz: Number of cubes in the z-direction.
|
|
4577
|
+
max_verts: Maximum expected number of vertices (used for array preallocation).
|
|
4578
|
+
max_tris: Maximum expected number of triangles (used for array preallocation).
|
|
4579
|
+
device (Devicelike): CUDA device on which to run marching cubes and allocate memory.
|
|
4580
|
+
|
|
4581
|
+
Raises:
|
|
4582
|
+
RuntimeError: ``device`` not a CUDA device.
|
|
4583
|
+
|
|
4584
|
+
.. note::
|
|
4585
|
+
The shape of the marching cubes should match the shape of the scalar field being surfaced.
|
|
4586
|
+
|
|
4587
|
+
"""
|
|
4588
|
+
|
|
4188
4589
|
self.id = 0
|
|
4189
4590
|
|
|
4190
4591
|
self.runtime = warp.context.runtime
|
|
@@ -4210,7 +4611,7 @@ class MarchingCubes:
|
|
|
4210
4611
|
from warp.context import zeros
|
|
4211
4612
|
|
|
4212
4613
|
self.verts = zeros(max_verts, dtype=vec3, device=self.device)
|
|
4213
|
-
self.indices = zeros(max_tris * 3, dtype=
|
|
4614
|
+
self.indices = zeros(max_tris * 3, dtype=warp.int32, device=self.device)
|
|
4214
4615
|
|
|
4215
4616
|
# alloc surfacer
|
|
4216
4617
|
self.id = ctypes.c_uint64(self.alloc(self.device.context))
|
|
@@ -4224,7 +4625,19 @@ class MarchingCubes:
|
|
|
4224
4625
|
# destroy surfacer
|
|
4225
4626
|
self.free(self.id)
|
|
4226
4627
|
|
|
4227
|
-
def resize(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int):
|
|
4628
|
+
def resize(self, nx: int, ny: int, nz: int, max_verts: int, max_tris: int) -> None:
|
|
4629
|
+
"""Update the expected input and maximum output sizes for the marching cubes calculation.
|
|
4630
|
+
|
|
4631
|
+
This function has no immediate effect on the underlying buffers.
|
|
4632
|
+
The new values take effect on the next :func:`surface` call.
|
|
4633
|
+
|
|
4634
|
+
Args:
|
|
4635
|
+
nx: Number of cubes in the x-direction.
|
|
4636
|
+
ny: Number of cubes in the y-direction.
|
|
4637
|
+
nz: Number of cubes in the z-direction.
|
|
4638
|
+
max_verts: Maximum expected number of vertices (used for array preallocation).
|
|
4639
|
+
max_tris: Maximum expected number of triangles (used for array preallocation).
|
|
4640
|
+
"""
|
|
4228
4641
|
# actual allocations will be resized on next call to surface()
|
|
4229
4642
|
self.nx = nx
|
|
4230
4643
|
self.ny = ny
|
|
@@ -4232,13 +4645,37 @@ class MarchingCubes:
|
|
|
4232
4645
|
self.max_verts = max_verts
|
|
4233
4646
|
self.max_tris = max_tris
|
|
4234
4647
|
|
|
4235
|
-
def surface(self, field: array(dtype=float), threshold: float):
|
|
4648
|
+
def surface(self, field: array(dtype=float, ndim=3), threshold: float) -> None:
|
|
4649
|
+
"""Compute a 2D surface mesh of a given isosurface from a 3D scalar field.
|
|
4650
|
+
|
|
4651
|
+
The triangles and vertices defining the output mesh are written to the
|
|
4652
|
+
:attr:`indices` and :attr:`verts` arrays.
|
|
4653
|
+
|
|
4654
|
+
Args:
|
|
4655
|
+
field: Scalar field from which to generate a mesh.
|
|
4656
|
+
threshold: Target isosurface value.
|
|
4657
|
+
|
|
4658
|
+
Raises:
|
|
4659
|
+
ValueError: ``field`` is not a 3D array.
|
|
4660
|
+
ValueError: Marching cubes shape does not match the shape of ``field``.
|
|
4661
|
+
RuntimeError: :attr:`max_verts` and/or :attr:`max_tris` might be too small to hold the surface mesh.
|
|
4662
|
+
"""
|
|
4663
|
+
|
|
4236
4664
|
# 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
4665
|
num_verts = ctypes.c_int(0)
|
|
4238
4666
|
num_tris = ctypes.c_int(0)
|
|
4239
4667
|
|
|
4240
4668
|
self.runtime.core.marching_cubes_surface_device.restype = ctypes.c_int
|
|
4241
4669
|
|
|
4670
|
+
# For now we require that input field shape matches nx, ny, nz
|
|
4671
|
+
if field.ndim != 3:
|
|
4672
|
+
raise ValueError(f"Input field must be a three-dimensional array (got {field.ndim}).")
|
|
4673
|
+
if field.shape[0] != self.nx or field.shape[1] != self.ny or field.shape[2] != self.nz:
|
|
4674
|
+
raise ValueError(
|
|
4675
|
+
f"Marching cubes shape ({self.nx}, {self.ny}, {self.nz}) does not match the "
|
|
4676
|
+
f"input array shape {field.shape}."
|
|
4677
|
+
)
|
|
4678
|
+
|
|
4242
4679
|
error = self.runtime.core.marching_cubes_surface_device(
|
|
4243
4680
|
self.id,
|
|
4244
4681
|
ctypes.cast(field.ptr, ctypes.c_void_p),
|
|
@@ -4362,7 +4799,7 @@ def infer_argument_types(args, template_types, arg_names=None):
|
|
|
4362
4799
|
arg_types.append(arg_type(dtype=arg.dtype, ndim=arg.ndim))
|
|
4363
4800
|
elif arg_type in warp.types.scalar_and_bool_types:
|
|
4364
4801
|
arg_types.append(arg_type)
|
|
4365
|
-
elif arg_type in (int, float):
|
|
4802
|
+
elif arg_type in (int, float, builtins.bool):
|
|
4366
4803
|
# canonicalize type
|
|
4367
4804
|
arg_types.append(warp.types.type_to_warp(arg_type))
|
|
4368
4805
|
elif hasattr(arg_type, "_wp_scalar_type_"):
|