warp-lang 1.9.1__py3-none-manylinux_2_34_aarch64.whl → 1.10.0rc2__py3-none-manylinux_2_34_aarch64.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.

Files changed (346) hide show
  1. warp/__init__.py +301 -287
  2. warp/__init__.pyi +794 -305
  3. warp/_src/__init__.py +14 -0
  4. warp/_src/autograd.py +1075 -0
  5. warp/_src/build.py +618 -0
  6. warp/_src/build_dll.py +640 -0
  7. warp/{builtins.py → _src/builtins.py} +1382 -377
  8. warp/_src/codegen.py +4359 -0
  9. warp/{config.py → _src/config.py} +178 -169
  10. warp/_src/constants.py +57 -0
  11. warp/_src/context.py +8294 -0
  12. warp/_src/dlpack.py +462 -0
  13. warp/_src/fabric.py +355 -0
  14. warp/_src/fem/__init__.py +14 -0
  15. warp/_src/fem/adaptivity.py +508 -0
  16. warp/_src/fem/cache.py +687 -0
  17. warp/_src/fem/dirichlet.py +188 -0
  18. warp/{fem → _src/fem}/domain.py +40 -30
  19. warp/_src/fem/field/__init__.py +131 -0
  20. warp/_src/fem/field/field.py +701 -0
  21. warp/{fem → _src/fem}/field/nodal_field.py +30 -15
  22. warp/{fem → _src/fem}/field/restriction.py +1 -1
  23. warp/{fem → _src/fem}/field/virtual.py +53 -27
  24. warp/_src/fem/geometry/__init__.py +32 -0
  25. warp/{fem → _src/fem}/geometry/adaptive_nanogrid.py +77 -163
  26. warp/_src/fem/geometry/closest_point.py +97 -0
  27. warp/{fem → _src/fem}/geometry/deformed_geometry.py +14 -22
  28. warp/{fem → _src/fem}/geometry/element.py +32 -10
  29. warp/{fem → _src/fem}/geometry/geometry.py +48 -20
  30. warp/{fem → _src/fem}/geometry/grid_2d.py +12 -23
  31. warp/{fem → _src/fem}/geometry/grid_3d.py +12 -23
  32. warp/{fem → _src/fem}/geometry/hexmesh.py +40 -63
  33. warp/{fem → _src/fem}/geometry/nanogrid.py +255 -248
  34. warp/{fem → _src/fem}/geometry/partition.py +121 -63
  35. warp/{fem → _src/fem}/geometry/quadmesh.py +26 -45
  36. warp/{fem → _src/fem}/geometry/tetmesh.py +40 -63
  37. warp/{fem → _src/fem}/geometry/trimesh.py +26 -45
  38. warp/{fem → _src/fem}/integrate.py +164 -158
  39. warp/_src/fem/linalg.py +383 -0
  40. warp/_src/fem/operator.py +396 -0
  41. warp/_src/fem/polynomial.py +229 -0
  42. warp/{fem → _src/fem}/quadrature/pic_quadrature.py +15 -20
  43. warp/{fem → _src/fem}/quadrature/quadrature.py +95 -47
  44. warp/_src/fem/space/__init__.py +248 -0
  45. warp/{fem → _src/fem}/space/basis_function_space.py +20 -11
  46. warp/_src/fem/space/basis_space.py +679 -0
  47. warp/{fem → _src/fem}/space/dof_mapper.py +3 -3
  48. warp/{fem → _src/fem}/space/function_space.py +14 -13
  49. warp/{fem → _src/fem}/space/grid_2d_function_space.py +4 -7
  50. warp/{fem → _src/fem}/space/grid_3d_function_space.py +4 -4
  51. warp/{fem → _src/fem}/space/hexmesh_function_space.py +4 -10
  52. warp/{fem → _src/fem}/space/nanogrid_function_space.py +3 -9
  53. warp/{fem → _src/fem}/space/partition.py +117 -60
  54. warp/{fem → _src/fem}/space/quadmesh_function_space.py +4 -10
  55. warp/{fem → _src/fem}/space/restriction.py +66 -33
  56. warp/_src/fem/space/shape/__init__.py +152 -0
  57. warp/{fem → _src/fem}/space/shape/cube_shape_function.py +9 -9
  58. warp/{fem → _src/fem}/space/shape/shape_function.py +8 -9
  59. warp/{fem → _src/fem}/space/shape/square_shape_function.py +6 -6
  60. warp/{fem → _src/fem}/space/shape/tet_shape_function.py +3 -3
  61. warp/{fem → _src/fem}/space/shape/triangle_shape_function.py +3 -3
  62. warp/{fem → _src/fem}/space/tetmesh_function_space.py +3 -9
  63. warp/_src/fem/space/topology.py +459 -0
  64. warp/{fem → _src/fem}/space/trimesh_function_space.py +3 -9
  65. warp/_src/fem/types.py +112 -0
  66. warp/_src/fem/utils.py +486 -0
  67. warp/_src/jax.py +186 -0
  68. warp/_src/jax_experimental/__init__.py +14 -0
  69. warp/_src/jax_experimental/custom_call.py +387 -0
  70. warp/_src/jax_experimental/ffi.py +1284 -0
  71. warp/_src/jax_experimental/xla_ffi.py +656 -0
  72. warp/_src/marching_cubes.py +708 -0
  73. warp/_src/math.py +414 -0
  74. warp/_src/optim/__init__.py +14 -0
  75. warp/_src/optim/adam.py +163 -0
  76. warp/_src/optim/linear.py +1606 -0
  77. warp/_src/optim/sgd.py +112 -0
  78. warp/_src/paddle.py +406 -0
  79. warp/_src/render/__init__.py +14 -0
  80. warp/_src/render/imgui_manager.py +289 -0
  81. warp/_src/render/render_opengl.py +3636 -0
  82. warp/_src/render/render_usd.py +937 -0
  83. warp/_src/render/utils.py +160 -0
  84. warp/_src/sparse.py +2716 -0
  85. warp/_src/tape.py +1206 -0
  86. warp/{thirdparty → _src/thirdparty}/unittest_parallel.py +9 -2
  87. warp/_src/torch.py +391 -0
  88. warp/_src/types.py +5870 -0
  89. warp/_src/utils.py +1693 -0
  90. warp/autograd.py +12 -1054
  91. warp/bin/warp-clang.so +0 -0
  92. warp/bin/warp.so +0 -0
  93. warp/build.py +8 -588
  94. warp/build_dll.py +6 -721
  95. warp/codegen.py +6 -4251
  96. warp/constants.py +6 -39
  97. warp/context.py +12 -8062
  98. warp/dlpack.py +6 -444
  99. warp/examples/distributed/example_jacobi_mpi.py +4 -5
  100. warp/examples/fem/example_adaptive_grid.py +1 -1
  101. warp/examples/fem/example_apic_fluid.py +1 -1
  102. warp/examples/fem/example_burgers.py +8 -8
  103. warp/examples/fem/example_diffusion.py +1 -1
  104. warp/examples/fem/example_distortion_energy.py +1 -1
  105. warp/examples/fem/example_mixed_elasticity.py +2 -2
  106. warp/examples/fem/example_navier_stokes.py +1 -1
  107. warp/examples/fem/example_nonconforming_contact.py +7 -7
  108. warp/examples/fem/example_stokes.py +1 -1
  109. warp/examples/fem/example_stokes_transfer.py +1 -1
  110. warp/examples/fem/utils.py +2 -2
  111. warp/examples/interop/example_jax_callable.py +1 -1
  112. warp/examples/interop/example_jax_ffi_callback.py +1 -1
  113. warp/examples/interop/example_jax_kernel.py +1 -1
  114. warp/examples/tile/example_tile_mcgp.py +191 -0
  115. warp/fabric.py +6 -337
  116. warp/fem/__init__.py +159 -97
  117. warp/fem/adaptivity.py +7 -489
  118. warp/fem/cache.py +9 -648
  119. warp/fem/dirichlet.py +6 -184
  120. warp/fem/field/__init__.py +8 -109
  121. warp/fem/field/field.py +7 -652
  122. warp/fem/geometry/__init__.py +7 -18
  123. warp/fem/geometry/closest_point.py +11 -77
  124. warp/fem/linalg.py +18 -366
  125. warp/fem/operator.py +11 -369
  126. warp/fem/polynomial.py +9 -209
  127. warp/fem/space/__init__.py +5 -211
  128. warp/fem/space/basis_space.py +6 -662
  129. warp/fem/space/shape/__init__.py +41 -118
  130. warp/fem/space/topology.py +6 -437
  131. warp/fem/types.py +6 -81
  132. warp/fem/utils.py +11 -444
  133. warp/jax.py +8 -165
  134. warp/jax_experimental/__init__.py +14 -1
  135. warp/jax_experimental/custom_call.py +8 -365
  136. warp/jax_experimental/ffi.py +17 -873
  137. warp/jax_experimental/xla_ffi.py +5 -605
  138. warp/marching_cubes.py +5 -689
  139. warp/math.py +16 -393
  140. warp/native/array.h +385 -37
  141. warp/native/builtin.h +314 -37
  142. warp/native/bvh.cpp +43 -9
  143. warp/native/bvh.cu +62 -27
  144. warp/native/bvh.h +310 -309
  145. warp/native/clang/clang.cpp +102 -97
  146. warp/native/coloring.cpp +0 -1
  147. warp/native/crt.h +208 -0
  148. warp/native/exports.h +156 -0
  149. warp/native/hashgrid.cu +2 -0
  150. warp/native/intersect.h +24 -1
  151. warp/native/intersect_tri.h +44 -35
  152. warp/native/mat.h +1456 -276
  153. warp/native/mesh.cpp +4 -4
  154. warp/native/mesh.cu +4 -2
  155. warp/native/mesh.h +176 -61
  156. warp/native/quat.h +0 -52
  157. warp/native/scan.cu +2 -0
  158. warp/native/sparse.cu +7 -3
  159. warp/native/spatial.h +12 -0
  160. warp/native/tile.h +681 -89
  161. warp/native/tile_radix_sort.h +1 -1
  162. warp/native/tile_reduce.h +394 -46
  163. warp/native/tile_scan.h +4 -4
  164. warp/native/vec.h +469 -0
  165. warp/native/version.h +23 -0
  166. warp/native/volume.cpp +1 -1
  167. warp/native/volume.cu +1 -0
  168. warp/native/volume.h +1 -1
  169. warp/native/volume_builder.cu +2 -0
  170. warp/native/warp.cpp +57 -29
  171. warp/native/warp.cu +253 -171
  172. warp/native/warp.h +11 -8
  173. warp/optim/__init__.py +6 -3
  174. warp/optim/adam.py +6 -145
  175. warp/optim/linear.py +14 -1585
  176. warp/optim/sgd.py +6 -94
  177. warp/paddle.py +6 -388
  178. warp/render/__init__.py +8 -4
  179. warp/render/imgui_manager.py +7 -267
  180. warp/render/render_opengl.py +6 -3618
  181. warp/render/render_usd.py +6 -919
  182. warp/render/utils.py +6 -142
  183. warp/sparse.py +37 -2563
  184. warp/tape.py +6 -1188
  185. warp/tests/__main__.py +1 -1
  186. warp/tests/cuda/test_async.py +4 -4
  187. warp/tests/cuda/test_conditional_captures.py +1 -1
  188. warp/tests/cuda/test_multigpu.py +1 -1
  189. warp/tests/cuda/test_streams.py +58 -1
  190. warp/tests/geometry/test_bvh.py +157 -22
  191. warp/tests/geometry/test_marching_cubes.py +0 -1
  192. warp/tests/geometry/test_mesh.py +5 -3
  193. warp/tests/geometry/test_mesh_query_aabb.py +5 -12
  194. warp/tests/geometry/test_mesh_query_point.py +5 -2
  195. warp/tests/geometry/test_mesh_query_ray.py +15 -3
  196. warp/tests/geometry/test_volume_write.py +5 -5
  197. warp/tests/interop/test_dlpack.py +14 -14
  198. warp/tests/interop/test_jax.py +772 -49
  199. warp/tests/interop/test_paddle.py +1 -1
  200. warp/tests/test_adam.py +0 -1
  201. warp/tests/test_arithmetic.py +9 -9
  202. warp/tests/test_array.py +527 -100
  203. warp/tests/test_array_reduce.py +3 -3
  204. warp/tests/test_atomic.py +12 -8
  205. warp/tests/test_atomic_bitwise.py +209 -0
  206. warp/tests/test_atomic_cas.py +4 -4
  207. warp/tests/test_bool.py +2 -2
  208. warp/tests/test_builtins_resolution.py +5 -571
  209. warp/tests/test_codegen.py +33 -14
  210. warp/tests/test_conditional.py +1 -1
  211. warp/tests/test_context.py +6 -6
  212. warp/tests/test_copy.py +242 -161
  213. warp/tests/test_ctypes.py +3 -3
  214. warp/tests/test_devices.py +24 -2
  215. warp/tests/test_examples.py +16 -84
  216. warp/tests/test_fabricarray.py +35 -35
  217. warp/tests/test_fast_math.py +0 -2
  218. warp/tests/test_fem.py +56 -10
  219. warp/tests/test_fixedarray.py +3 -3
  220. warp/tests/test_func.py +8 -5
  221. warp/tests/test_generics.py +1 -1
  222. warp/tests/test_indexedarray.py +24 -24
  223. warp/tests/test_intersect.py +39 -9
  224. warp/tests/test_large.py +1 -1
  225. warp/tests/test_lerp.py +3 -1
  226. warp/tests/test_linear_solvers.py +1 -1
  227. warp/tests/test_map.py +35 -4
  228. warp/tests/test_mat.py +52 -62
  229. warp/tests/test_mat_constructors.py +4 -5
  230. warp/tests/test_mat_lite.py +1 -1
  231. warp/tests/test_mat_scalar_ops.py +121 -121
  232. warp/tests/test_math.py +34 -0
  233. warp/tests/test_module_aot.py +4 -4
  234. warp/tests/test_modules_lite.py +28 -2
  235. warp/tests/test_print.py +11 -11
  236. warp/tests/test_quat.py +93 -58
  237. warp/tests/test_runlength_encode.py +1 -1
  238. warp/tests/test_scalar_ops.py +38 -10
  239. warp/tests/test_smoothstep.py +1 -1
  240. warp/tests/test_sparse.py +126 -15
  241. warp/tests/test_spatial.py +105 -87
  242. warp/tests/test_special_values.py +6 -6
  243. warp/tests/test_static.py +7 -7
  244. warp/tests/test_struct.py +13 -2
  245. warp/tests/test_triangle_closest_point.py +48 -1
  246. warp/tests/test_types.py +27 -15
  247. warp/tests/test_utils.py +52 -52
  248. warp/tests/test_vec.py +29 -29
  249. warp/tests/test_vec_constructors.py +5 -5
  250. warp/tests/test_vec_scalar_ops.py +97 -97
  251. warp/tests/test_version.py +75 -0
  252. warp/tests/tile/test_tile.py +178 -0
  253. warp/tests/tile/test_tile_atomic_bitwise.py +403 -0
  254. warp/tests/tile/test_tile_cholesky.py +7 -4
  255. warp/tests/tile/test_tile_load.py +26 -2
  256. warp/tests/tile/test_tile_mathdx.py +3 -3
  257. warp/tests/tile/test_tile_matmul.py +1 -1
  258. warp/tests/tile/test_tile_mlp.py +2 -4
  259. warp/tests/tile/test_tile_reduce.py +214 -13
  260. warp/tests/unittest_suites.py +6 -14
  261. warp/tests/unittest_utils.py +10 -9
  262. warp/tests/walkthrough_debug.py +3 -1
  263. warp/torch.py +6 -373
  264. warp/types.py +29 -5764
  265. warp/utils.py +10 -1659
  266. {warp_lang-1.9.1.dist-info → warp_lang-1.10.0rc2.dist-info}/METADATA +46 -99
  267. warp_lang-1.10.0rc2.dist-info/RECORD +468 -0
  268. warp_lang-1.10.0rc2.dist-info/licenses/licenses/Gaia-LICENSE.txt +6 -0
  269. warp_lang-1.10.0rc2.dist-info/licenses/licenses/appdirs-LICENSE.txt +22 -0
  270. warp_lang-1.10.0rc2.dist-info/licenses/licenses/asset_pixel_jpg-LICENSE.txt +3 -0
  271. warp_lang-1.10.0rc2.dist-info/licenses/licenses/cuda-LICENSE.txt +1582 -0
  272. warp_lang-1.10.0rc2.dist-info/licenses/licenses/dlpack-LICENSE.txt +201 -0
  273. warp_lang-1.10.0rc2.dist-info/licenses/licenses/fp16-LICENSE.txt +28 -0
  274. warp_lang-1.10.0rc2.dist-info/licenses/licenses/libmathdx-LICENSE.txt +220 -0
  275. warp_lang-1.10.0rc2.dist-info/licenses/licenses/llvm-LICENSE.txt +279 -0
  276. warp_lang-1.10.0rc2.dist-info/licenses/licenses/moller-LICENSE.txt +16 -0
  277. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nanovdb-LICENSE.txt +2 -0
  278. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nvrtc-LICENSE.txt +1592 -0
  279. warp_lang-1.10.0rc2.dist-info/licenses/licenses/svd-LICENSE.txt +23 -0
  280. warp_lang-1.10.0rc2.dist-info/licenses/licenses/unittest_parallel-LICENSE.txt +21 -0
  281. warp_lang-1.10.0rc2.dist-info/licenses/licenses/usd-LICENSE.txt +213 -0
  282. warp_lang-1.10.0rc2.dist-info/licenses/licenses/windingnumber-LICENSE.txt +21 -0
  283. warp/examples/assets/cartpole.urdf +0 -110
  284. warp/examples/assets/crazyflie.usd +0 -0
  285. warp/examples/assets/nv_ant.xml +0 -92
  286. warp/examples/assets/nv_humanoid.xml +0 -183
  287. warp/examples/assets/quadruped.urdf +0 -268
  288. warp/examples/optim/example_bounce.py +0 -266
  289. warp/examples/optim/example_cloth_throw.py +0 -228
  290. warp/examples/optim/example_drone.py +0 -870
  291. warp/examples/optim/example_inverse_kinematics.py +0 -182
  292. warp/examples/optim/example_inverse_kinematics_torch.py +0 -191
  293. warp/examples/optim/example_softbody_properties.py +0 -400
  294. warp/examples/optim/example_spring_cage.py +0 -245
  295. warp/examples/optim/example_trajectory.py +0 -227
  296. warp/examples/sim/example_cartpole.py +0 -143
  297. warp/examples/sim/example_cloth.py +0 -225
  298. warp/examples/sim/example_cloth_self_contact.py +0 -316
  299. warp/examples/sim/example_granular.py +0 -130
  300. warp/examples/sim/example_granular_collision_sdf.py +0 -202
  301. warp/examples/sim/example_jacobian_ik.py +0 -244
  302. warp/examples/sim/example_particle_chain.py +0 -124
  303. warp/examples/sim/example_quadruped.py +0 -203
  304. warp/examples/sim/example_rigid_chain.py +0 -203
  305. warp/examples/sim/example_rigid_contact.py +0 -195
  306. warp/examples/sim/example_rigid_force.py +0 -133
  307. warp/examples/sim/example_rigid_gyroscopic.py +0 -115
  308. warp/examples/sim/example_rigid_soft_contact.py +0 -140
  309. warp/examples/sim/example_soft_body.py +0 -196
  310. warp/examples/tile/example_tile_walker.py +0 -327
  311. warp/sim/__init__.py +0 -74
  312. warp/sim/articulation.py +0 -793
  313. warp/sim/collide.py +0 -2570
  314. warp/sim/graph_coloring.py +0 -307
  315. warp/sim/import_mjcf.py +0 -791
  316. warp/sim/import_snu.py +0 -227
  317. warp/sim/import_urdf.py +0 -579
  318. warp/sim/import_usd.py +0 -898
  319. warp/sim/inertia.py +0 -357
  320. warp/sim/integrator.py +0 -245
  321. warp/sim/integrator_euler.py +0 -2000
  322. warp/sim/integrator_featherstone.py +0 -2101
  323. warp/sim/integrator_vbd.py +0 -2487
  324. warp/sim/integrator_xpbd.py +0 -3295
  325. warp/sim/model.py +0 -4821
  326. warp/sim/particles.py +0 -121
  327. warp/sim/render.py +0 -431
  328. warp/sim/utils.py +0 -431
  329. warp/tests/sim/disabled_kinematics.py +0 -244
  330. warp/tests/sim/test_cloth.py +0 -863
  331. warp/tests/sim/test_collision.py +0 -743
  332. warp/tests/sim/test_coloring.py +0 -347
  333. warp/tests/sim/test_inertia.py +0 -161
  334. warp/tests/sim/test_model.py +0 -226
  335. warp/tests/sim/test_sim_grad.py +0 -287
  336. warp/tests/sim/test_sim_grad_bounce_linear.py +0 -212
  337. warp/tests/sim/test_sim_kinematics.py +0 -98
  338. warp/thirdparty/__init__.py +0 -0
  339. warp_lang-1.9.1.dist-info/RECORD +0 -456
  340. /warp/{fem → _src/fem}/quadrature/__init__.py +0 -0
  341. /warp/{tests/sim → _src/thirdparty}/__init__.py +0 -0
  342. /warp/{thirdparty → _src/thirdparty}/appdirs.py +0 -0
  343. /warp/{thirdparty → _src/thirdparty}/dlpack.py +0 -0
  344. {warp_lang-1.9.1.dist-info → warp_lang-1.10.0rc2.dist-info}/WHEEL +0 -0
  345. {warp_lang-1.9.1.dist-info → warp_lang-1.10.0rc2.dist-info}/licenses/LICENSE.md +0 -0
  346. {warp_lang-1.9.1.dist-info → warp_lang-1.10.0rc2.dist-info}/top_level.txt +0 -0
warp/sim/model.py DELETED
@@ -1,4821 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
- # SPDX-License-Identifier: Apache-2.0
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
-
16
- """A module for building simulation models and state."""
17
-
18
- from __future__ import annotations
19
-
20
- import copy
21
- import math
22
- from typing import List, Tuple
23
-
24
- import numpy as np
25
-
26
- import warp as wp
27
-
28
- from .graph_coloring import ColoringAlgorithm, color_trimesh, combine_independent_particle_coloring
29
- from .inertia import (
30
- compute_box_inertia,
31
- compute_capsule_inertia,
32
- compute_cone_inertia,
33
- compute_cylinder_inertia,
34
- compute_mesh_inertia,
35
- compute_sphere_inertia,
36
- transform_inertia,
37
- )
38
-
39
- Vec3 = List[float]
40
- Vec4 = List[float]
41
- Quat = List[float]
42
- Mat33 = List[float]
43
- Transform = Tuple[Vec3, Quat]
44
-
45
- # Particle flags
46
- PARTICLE_FLAG_ACTIVE = wp.constant(wp.uint32(1 << 0))
47
-
48
- # Shape geometry types
49
- GEO_SPHERE = wp.constant(0)
50
- GEO_BOX = wp.constant(1)
51
- GEO_CAPSULE = wp.constant(2)
52
- GEO_CYLINDER = wp.constant(3)
53
- GEO_CONE = wp.constant(4)
54
- GEO_MESH = wp.constant(5)
55
- GEO_SDF = wp.constant(6)
56
- GEO_PLANE = wp.constant(7)
57
- GEO_NONE = wp.constant(8)
58
-
59
- # Types of joints linking rigid bodies
60
- JOINT_PRISMATIC = wp.constant(0)
61
- JOINT_REVOLUTE = wp.constant(1)
62
- JOINT_BALL = wp.constant(2)
63
- JOINT_FIXED = wp.constant(3)
64
- JOINT_FREE = wp.constant(4)
65
- JOINT_COMPOUND = wp.constant(5)
66
- JOINT_UNIVERSAL = wp.constant(6)
67
- JOINT_DISTANCE = wp.constant(7)
68
- JOINT_D6 = wp.constant(8)
69
-
70
- # Joint axis control mode types
71
- JOINT_MODE_FORCE = wp.constant(0)
72
- JOINT_MODE_TARGET_POSITION = wp.constant(1)
73
- JOINT_MODE_TARGET_VELOCITY = wp.constant(2)
74
-
75
-
76
- def flag_to_int(flag):
77
- """Converts a flag to an integer."""
78
- if type(flag) in wp.types.int_types:
79
- return flag.value
80
- return int(flag)
81
-
82
-
83
- # Material properties pertaining to rigid shape contact dynamics
84
- @wp.struct
85
- class ModelShapeMaterials:
86
- ke: wp.array(dtype=float) # The contact elastic stiffness (only used by the Euler integrators)
87
- kd: wp.array(dtype=float) # The contact damping stiffness (only used by the Euler integrators)
88
- kf: wp.array(dtype=float) # The contact friction stiffness (only used by the Euler integrators)
89
- ka: wp.array(
90
- dtype=float
91
- ) # The contact adhesion distance (values greater than 0 mean adhesive contact; only used by the Euler integrators)
92
- mu: wp.array(dtype=float) # The coefficient of friction
93
- restitution: wp.array(dtype=float) # The coefficient of restitution (only used by XPBD integrator)
94
-
95
-
96
- # Shape properties of geometry
97
- @wp.struct
98
- class ModelShapeGeometry:
99
- type: wp.array(dtype=wp.int32) # The type of geometry (GEO_SPHERE, GEO_BOX, etc.)
100
- is_solid: wp.array(dtype=wp.uint8) # Indicates whether the shape is solid or hollow
101
- thickness: wp.array(
102
- dtype=float
103
- ) # The thickness of the shape (used for collision detection, and inertia computation of hollow shapes)
104
- source: wp.array(dtype=wp.uint64) # Pointer to the source geometry (in case of a mesh, zero otherwise)
105
- scale: wp.array(dtype=wp.vec3) # The 3D scale of the shape
106
-
107
-
108
- # Axis (linear or angular) of a joint that can have bounds and be driven towards a target
109
- class JointAxis:
110
- """
111
- Describes a joint axis that can have limits and be driven towards a target.
112
-
113
- Attributes:
114
-
115
- axis (3D vector or JointAxis): The 3D axis that this JointAxis object describes, or alternatively another JointAxis object to copy from
116
- limit_lower (float): The lower position limit of the joint axis
117
- limit_upper (float): The upper position limit of the joint axis
118
- limit_ke (float): The elastic stiffness of the joint axis limits, only respected by :class:`SemiImplicitIntegrator` and :class:`FeatherstoneIntegrator`
119
- limit_kd (float): The damping stiffness of the joint axis limits, only respected by :class:`SemiImplicitIntegrator` and :class:`FeatherstoneIntegrator`
120
- action (float): The force applied by default to this joint axis, or the target position or velocity (depending on the mode, see `Joint modes`_) of the joint axis
121
- target_ke (float): The proportional gain of the joint axis target drive PD controller
122
- target_kd (float): The derivative gain of the joint axis target drive PD controller
123
- mode (int): The mode of the joint axis, see `Joint modes`_
124
- """
125
-
126
- def __init__(
127
- self,
128
- axis,
129
- limit_lower=-math.inf,
130
- limit_upper=math.inf,
131
- limit_ke=100.0,
132
- limit_kd=10.0,
133
- action=None,
134
- target_ke=0.0,
135
- target_kd=0.0,
136
- mode=JOINT_MODE_FORCE,
137
- ):
138
- if isinstance(axis, JointAxis):
139
- self.axis = axis.axis
140
- self.limit_lower = axis.limit_lower
141
- self.limit_upper = axis.limit_upper
142
- self.limit_ke = axis.limit_ke
143
- self.limit_kd = axis.limit_kd
144
- self.action = axis.action
145
- self.target_ke = axis.target_ke
146
- self.target_kd = axis.target_kd
147
- self.mode = axis.mode
148
- else:
149
- self.axis = wp.normalize(wp.vec3(axis))
150
- self.limit_lower = limit_lower
151
- self.limit_upper = limit_upper
152
- self.limit_ke = limit_ke
153
- self.limit_kd = limit_kd
154
- if action is not None:
155
- self.action = action
156
- elif mode == JOINT_MODE_TARGET_POSITION and (limit_lower > 0.0 or limit_upper < 0.0):
157
- self.action = 0.5 * (limit_lower + limit_upper)
158
- else:
159
- self.action = 0.0
160
- self.target_ke = target_ke
161
- self.target_kd = target_kd
162
- self.mode = mode
163
-
164
-
165
- class SDF:
166
- """Describes a signed distance field for simulation
167
-
168
- Attributes:
169
-
170
- volume (Volume): The volume defining the SDF
171
- I (Mat33): 3x3 inertia matrix of the SDF
172
- mass (float): The total mass of the SDF
173
- com (Vec3): The center of mass of the SDF
174
- """
175
-
176
- def __init__(self, volume=None, I=None, mass=1.0, com=None):
177
- self.volume = volume
178
- self.I = I if I is not None else wp.mat33(np.eye(3))
179
- self.mass = mass
180
- self.com = com if com is not None else wp.vec3()
181
-
182
- # Need to specify these for now
183
- self.has_inertia = True
184
- self.is_solid = True
185
-
186
- def finalize(self, device=None):
187
- return self.volume.id
188
-
189
- def __hash__(self):
190
- return hash(self.volume.id)
191
-
192
-
193
- class Mesh:
194
- """Describes a triangle collision mesh for simulation
195
-
196
- Example mesh creation from a triangle OBJ mesh file:
197
- ====================================================
198
-
199
- See :func:`load_mesh` which is provided as a utility function.
200
-
201
- .. code-block:: python
202
-
203
- import numpy as np
204
- import warp as wp
205
- import warp.sim
206
- import openmesh
207
-
208
- m = openmesh.read_trimesh("mesh.obj")
209
- mesh_points = np.array(m.points())
210
- mesh_indices = np.array(m.face_vertex_indices(), dtype=np.int32).flatten()
211
- mesh = wp.sim.Mesh(mesh_points, mesh_indices)
212
-
213
- Attributes:
214
-
215
- vertices (List[Vec3]): Mesh 3D vertices points
216
- indices (List[int]): Mesh indices as a flattened list of vertex indices describing triangles
217
- I (Mat33): 3x3 inertia matrix of the mesh assuming density of 1.0 (around the center of mass)
218
- mass (float): The total mass of the body assuming density of 1.0
219
- com (Vec3): The center of mass of the body
220
- """
221
-
222
- def __init__(self, vertices: list[Vec3], indices: list[int], compute_inertia=True, is_solid=True):
223
- """Construct a Mesh object from a triangle mesh
224
-
225
- The mesh center of mass and inertia tensor will automatically be
226
- calculated using a density of 1.0. This computation is only valid
227
- if the mesh is closed (two-manifold).
228
-
229
- Args:
230
- vertices: List of vertices in the mesh
231
- indices: List of triangle indices, 3 per-element
232
- compute_inertia: If True, the mass, inertia tensor and center of mass will be computed assuming density of 1.0
233
- is_solid: If True, the mesh is assumed to be a solid during inertia computation, otherwise it is assumed to be a hollow surface
234
- """
235
-
236
- self.vertices = np.array(vertices).reshape(-1, 3)
237
- self.indices = np.array(indices, dtype=np.int32).flatten()
238
- self.is_solid = is_solid
239
- self.has_inertia = compute_inertia
240
-
241
- if compute_inertia:
242
- self.mass, self.com, self.I, _ = compute_mesh_inertia(1.0, vertices, indices, is_solid=is_solid)
243
- else:
244
- self.I = wp.mat33(np.eye(3))
245
- self.mass = 1.0
246
- self.com = wp.vec3()
247
-
248
- # construct simulation ready buffers from points
249
- def finalize(self, device=None):
250
- """
251
- Constructs a simulation-ready :class:`Mesh` object from the mesh data and returns its ID.
252
-
253
- Args:
254
- device: The device on which to allocate the mesh buffers
255
-
256
- Returns:
257
- The ID of the simulation-ready :class:`Mesh`
258
- """
259
- with wp.ScopedDevice(device):
260
- pos = wp.array(self.vertices, dtype=wp.vec3)
261
- vel = wp.zeros_like(pos)
262
- indices = wp.array(self.indices, dtype=wp.int32)
263
-
264
- self.mesh = wp.Mesh(points=pos, velocities=vel, indices=indices)
265
- return self.mesh.id
266
-
267
- def __hash__(self):
268
- """
269
- Computes a hash of the mesh data for use in caching. The hash considers the mesh vertices, indices, and whether the mesh is solid or not.
270
- """
271
- return hash((tuple(np.array(self.vertices).flatten()), tuple(np.array(self.indices).flatten()), self.is_solid))
272
-
273
-
274
- class State:
275
- """Time-varying state data for a :class:`Model`.
276
-
277
- Time-varying state data includes particle positions, velocities, rigid body states, and
278
- anything that is output from the integrator as derived data, e.g.: forces.
279
-
280
- The exact attributes depend on the contents of the model. State objects should
281
- generally be created using the :func:`Model.state()` function.
282
- """
283
-
284
- def __init__(self):
285
- self.particle_q: wp.array | None = None
286
- """Array of 3D particle positions with shape ``(particle_count,)`` and type :class:`vec3`."""
287
-
288
- self.particle_qd: wp.array | None = None
289
- """Array of 3D particle velocities with shape ``(particle_count,)`` and type :class:`vec3`."""
290
-
291
- self.particle_f: wp.array | None = None
292
- """Array of 3D particle forces with shape ``(particle_count,)`` and type :class:`vec3`."""
293
-
294
- self.body_q: wp.array | None = None
295
- """Array of body coordinates (7-dof transforms) in maximal coordinates with shape ``(body_count,)`` and type :class:`transform`."""
296
-
297
- self.body_qd: wp.array | None = None
298
- """Array of body velocities in maximal coordinates (first three entries represent angular velocity,
299
- last three entries represent linear velocity) with shape ``(body_count,)`` and type :class:`spatial_vector`.
300
- """
301
-
302
- self.body_f: wp.array | None = None
303
- """Array of body forces in maximal coordinates (first three entries represent torque, last three
304
- entries represent linear force) with shape ``(body_count,)`` and type :class:`spatial_vector`.
305
-
306
- .. note::
307
- :attr:`body_f` represents external wrenches in world frame and denotes wrenches measured w.r.t.
308
- to the body's center of mass for all integrators except :class:`FeatherstoneIntegrator`, which
309
- assumes the wrenches are measured w.r.t. world origin.
310
- """
311
-
312
- self.joint_q: wp.array | None = None
313
- """Array of generalized joint coordinates with shape ``(joint_coord_count,)`` and type ``float``."""
314
-
315
- self.joint_qd: wp.array | None = None
316
- """Array of generalized joint velocities with shape ``(joint_dof_count,)`` and type ``float``."""
317
-
318
- def clear_forces(self) -> None:
319
- """Clear all forces (for particles and bodies) in the state object."""
320
- with wp.ScopedTimer("clear_forces", False):
321
- if self.particle_count:
322
- self.particle_f.zero_()
323
-
324
- if self.body_count:
325
- self.body_f.zero_()
326
-
327
- @property
328
- def requires_grad(self) -> bool:
329
- """Indicates whether the state arrays have gradient computation enabled."""
330
- if self.particle_q:
331
- return self.particle_q.requires_grad
332
- if self.body_q:
333
- return self.body_q.requires_grad
334
- return False
335
-
336
- @property
337
- def body_count(self) -> int:
338
- """The number of bodies represented in the state."""
339
- if self.body_q is None:
340
- return 0
341
- return len(self.body_q)
342
-
343
- @property
344
- def particle_count(self) -> int:
345
- """The number of particles represented in the state."""
346
- if self.particle_q is None:
347
- return 0
348
- return len(self.particle_q)
349
-
350
- @property
351
- def joint_coord_count(self) -> int:
352
- """The number of generalized joint position coordinates represented in the state."""
353
- if self.joint_q is None:
354
- return 0
355
- return len(self.joint_q)
356
-
357
- @property
358
- def joint_dof_count(self) -> int:
359
- """The number of generalized joint velocity coordinates represented in the state."""
360
- if self.joint_qd is None:
361
- return 0
362
- return len(self.joint_qd)
363
-
364
-
365
- class Control:
366
- """Time-varying control data for a :class:`Model`.
367
-
368
- Time-varying control data includes joint control inputs, muscle activations,
369
- and activation forces for triangle and tetrahedral elements.
370
-
371
- The exact attributes depend on the contents of the model. Control objects
372
- should generally be created using the :func:`Model.control()` function.
373
- """
374
-
375
- def __init__(self, model: Model | None = None):
376
- if model:
377
- wp.utils.warn(
378
- "Passing arguments to Control's __init__ is deprecated\n"
379
- "and will be disallowed in a future version. Use Control() without arguments\ninstead.",
380
- category=DeprecationWarning,
381
- stacklevel=2,
382
- )
383
-
384
- self.joint_act: wp.array | None = None
385
- """Array of joint control inputs with shape ``(joint_axis_count,)`` and type ``float``."""
386
-
387
- self.tri_activations: wp.array | None = None
388
- """Array of triangle element activations with shape ``(tri_count,)`` and type ``float``."""
389
-
390
- self.tet_activations: wp.array | None = None
391
- """Array of tetrahedral element activations with shape with shape ``(tet_count,) and type ``float``."""
392
-
393
- self.muscle_activations: wp.array | None = None
394
- """Array of muscle activations with shape ``(muscle_count,)`` and type ``float``."""
395
-
396
- def clear(self) -> None:
397
- """Reset the control inputs to zero."""
398
-
399
- if self.joint_act is not None:
400
- self.joint_act.zero_()
401
- if self.tri_activations is not None:
402
- self.tri_activations.zero_()
403
- if self.tet_activations is not None:
404
- self.tet_activations.zero_()
405
- if self.muscle_activations is not None:
406
- self.muscle_activations.zero_()
407
-
408
- def reset(self) -> None:
409
- """Reset the control inputs to zero."""
410
-
411
- wp.utils.warn(
412
- "Control.reset() is deprecated and will be removed\nin a future version. Use Control.clear() instead.",
413
- category=DeprecationWarning,
414
- stacklevel=2,
415
- )
416
-
417
- self.clear()
418
-
419
-
420
- def compute_shape_mass(type, scale, src, density, is_solid, thickness):
421
- """Computes the mass, center of mass and 3x3 inertia tensor of a shape
422
-
423
- Args:
424
- type: The type of shape (GEO_SPHERE, GEO_BOX, etc.)
425
- scale: The scale of the shape
426
- src: The source shape (Mesh or SDF)
427
- density: The density of the shape
428
- is_solid: Whether the shape is solid or hollow
429
- thickness: The thickness of the shape (used for collision detection, and inertia computation of hollow shapes)
430
-
431
- Returns:
432
- The mass, center of mass and 3x3 inertia tensor of the shape
433
- """
434
- if density == 0.0 or type == GEO_PLANE: # zero density means fixed
435
- return 0.0, wp.vec3(), wp.mat33()
436
-
437
- if type == GEO_SPHERE:
438
- solid = compute_sphere_inertia(density, scale[0])
439
- if is_solid:
440
- return solid
441
- else:
442
- hollow = compute_sphere_inertia(density, scale[0] - thickness)
443
- return solid[0] - hollow[0], solid[1], solid[2] - hollow[2]
444
- elif type == GEO_BOX:
445
- w, h, d = scale * 2.0
446
- solid = compute_box_inertia(density, w, h, d)
447
- if is_solid:
448
- return solid
449
- else:
450
- hollow = compute_box_inertia(density, w - thickness, h - thickness, d - thickness)
451
- return solid[0] - hollow[0], solid[1], solid[2] - hollow[2]
452
- elif type == GEO_CAPSULE:
453
- r, h = scale[0], scale[1] * 2.0
454
- solid = compute_capsule_inertia(density, r, h)
455
- if is_solid:
456
- return solid
457
- else:
458
- hollow = compute_capsule_inertia(density, r - thickness, h - 2.0 * thickness)
459
- return solid[0] - hollow[0], solid[1], solid[2] - hollow[2]
460
- elif type == GEO_CYLINDER:
461
- r, h = scale[0], scale[1] * 2.0
462
- solid = compute_cylinder_inertia(density, r, h)
463
- if is_solid:
464
- return solid
465
- else:
466
- hollow = compute_cylinder_inertia(density, r - thickness, h - 2.0 * thickness)
467
- return solid[0] - hollow[0], solid[1], solid[2] - hollow[2]
468
- elif type == GEO_CONE:
469
- r, h = scale[0], scale[1] * 2.0
470
- solid = compute_cone_inertia(density, r, h)
471
- if is_solid:
472
- return solid
473
- else:
474
- hollow = compute_cone_inertia(density, r - thickness, h - 2.0 * thickness)
475
- return solid[0] - hollow[0], solid[1], solid[2] - hollow[2]
476
- elif type == GEO_MESH or type == GEO_SDF:
477
- if src.has_inertia and src.mass > 0.0 and src.is_solid == is_solid:
478
- m, c, I = src.mass, src.com, src.I
479
-
480
- sx, sy, sz = scale
481
-
482
- mass_ratio = sx * sy * sz * density
483
- m_new = m * mass_ratio
484
-
485
- c_new = wp.cw_mul(c, scale)
486
-
487
- Ixx = I[0, 0] * (sy**2 + sz**2) / 2 * mass_ratio
488
- Iyy = I[1, 1] * (sx**2 + sz**2) / 2 * mass_ratio
489
- Izz = I[2, 2] * (sx**2 + sy**2) / 2 * mass_ratio
490
- Ixy = I[0, 1] * sx * sy * mass_ratio
491
- Ixz = I[0, 2] * sx * sz * mass_ratio
492
- Iyz = I[1, 2] * sy * sz * mass_ratio
493
-
494
- I_new = wp.mat33([[Ixx, Ixy, Ixz], [Ixy, Iyy, Iyz], [Ixz, Iyz, Izz]])
495
-
496
- return m_new, c_new, I_new
497
- elif type == GEO_MESH:
498
- # fall back to computing inertia from mesh geometry
499
- vertices = np.array(src.vertices) * np.array(scale)
500
- m, c, I, vol = compute_mesh_inertia(density, vertices, src.indices, is_solid, thickness)
501
- return m, c, I
502
- raise ValueError(f"Unsupported shape type: {type}")
503
-
504
-
505
- class Model:
506
- """Holds the definition of the simulation model
507
-
508
- This class holds the non-time varying description of the system, i.e.:
509
- all geometry, constraints, and parameters used to describe the simulation.
510
-
511
- Attributes:
512
- requires_grad (float): Indicates whether the model was finalized (see :meth:`ModelBuilder.finalize`) with gradient computation enabled
513
- num_envs (int): Number of articulation environments that were added to the ModelBuilder via `add_builder`
514
-
515
- particle_q (array): Particle positions, shape [particle_count, 3], float
516
- particle_qd (array): Particle velocities, shape [particle_count, 3], float
517
- particle_mass (array): Particle mass, shape [particle_count], float
518
- particle_inv_mass (array): Particle inverse mass, shape [particle_count], float
519
- particle_radius (array): Particle radius, shape [particle_count], float
520
- particle_max_radius (float): Maximum particle radius (useful for HashGrid construction)
521
- particle_ke (array): Particle normal contact stiffness (used by :class:`SemiImplicitIntegrator`), shape [particle_count], float
522
- particle_kd (array): Particle normal contact damping (used by :class:`SemiImplicitIntegrator`), shape [particle_count], float
523
- particle_kf (array): Particle friction force stiffness (used by :class:`SemiImplicitIntegrator`), shape [particle_count], float
524
- particle_mu (array): Particle friction coefficient, shape [particle_count], float
525
- particle_cohesion (array): Particle cohesion strength, shape [particle_count], float
526
- particle_adhesion (array): Particle adhesion strength, shape [particle_count], float
527
- particle_grid (HashGrid): HashGrid instance used for accelerated simulation of particle interactions
528
- particle_flags (array): Particle enabled state, shape [particle_count], bool
529
- particle_max_velocity (float): Maximum particle velocity (to prevent instability)
530
-
531
- shape_transform (array): Rigid shape transforms, shape [shape_count, 7], float
532
- shape_visible (array): Rigid shape visibility, shape [shape_count], bool
533
- shape_body (array): Rigid shape body index, shape [shape_count], int
534
- body_shapes (dict): Mapping from body index to list of attached shape indices
535
- shape_materials (ModelShapeMaterials): Rigid shape contact materials, shape [shape_count], float
536
- shape_shape_geo (ModelShapeGeometry): Shape geometry properties (geo type, scale, thickness, etc.), shape [shape_count, 3], float
537
- shape_geo_src (list): List of `wp.Mesh` instances used for rendering of mesh geometry
538
-
539
- shape_collision_group (list): Collision group of each shape, shape [shape_count], int
540
- shape_collision_group_map (dict): Mapping from collision group to list of shape indices
541
- shape_collision_filter_pairs (set): Pairs of shape indices that should not collide
542
- shape_collision_radius (array): Collision radius of each shape used for bounding sphere broadphase collision checking, shape [shape_count], float
543
- shape_ground_collision (list): Indicates whether each shape should collide with the ground, shape [shape_count], bool
544
- shape_shape_collision (list): Indicates whether each shape should collide with any other shape, shape [shape_count], bool
545
- shape_contact_pairs (array): Pairs of shape indices that may collide, shape [contact_pair_count, 2], int
546
- shape_ground_contact_pairs (array): Pairs of shape, ground indices that may collide, shape [ground_contact_pair_count, 2], int
547
-
548
- spring_indices (array): Particle spring indices, shape [spring_count*2], int
549
- spring_rest_length (array): Particle spring rest length, shape [spring_count], float
550
- spring_stiffness (array): Particle spring stiffness, shape [spring_count], float
551
- spring_damping (array): Particle spring damping, shape [spring_count], float
552
- spring_control (array): Particle spring activation, shape [spring_count], float
553
-
554
- tri_indices (array): Triangle element indices, shape [tri_count*3], int
555
- tri_poses (array): Triangle element rest pose, shape [tri_count, 2, 2], float
556
- tri_activations (array): Triangle element activations, shape [tri_count], float
557
- tri_materials (array): Triangle element materials, shape [tri_count, 5], float
558
- tri_areas (array): Triangle element rest areas, shape [tri_count], float
559
-
560
- edge_indices (array): Bending edge indices, shape [edge_count*4], int, each row is [o0, o1, v1, v2], where v1, v2 are on the edge
561
- edge_rest_angle (array): Bending edge rest angle, shape [edge_count], float
562
- edge_rest_length (array): Bending edge rest length, shape [edge_count], float
563
- edge_bending_properties (array): Bending edge stiffness and damping parameters, shape [edge_count, 2], float
564
-
565
- tet_indices (array): Tetrahedral element indices, shape [tet_count*4], int
566
- tet_poses (array): Tetrahedral rest poses, shape [tet_count, 3, 3], float
567
- tet_activations (array): Tetrahedral volumetric activations, shape [tet_count], float
568
- tet_materials (array): Tetrahedral elastic parameters in form :math:`k_{mu}, k_{lambda}, k_{damp}`, shape [tet_count, 3]
569
-
570
- muscle_start (array): Start index of the first muscle point per muscle, shape [muscle_count], int
571
- muscle_params (array): Muscle parameters, shape [muscle_count, 5], float
572
- muscle_bodies (array): Body indices of the muscle waypoints, int
573
- muscle_points (array): Local body offset of the muscle waypoints, float
574
- muscle_activations (array): Muscle activations, shape [muscle_count], float
575
-
576
- body_q (array): Poses of rigid bodies used for state initialization, shape [body_count, 7], float
577
- body_qd (array): Velocities of rigid bodies used for state initialization, shape [body_count, 6], float
578
- body_com (array): Rigid body center of mass (in local frame), shape [body_count, 7], float
579
- body_inertia (array): Rigid body inertia tensor (relative to COM), shape [body_count, 3, 3], float
580
- body_inv_inertia (array): Rigid body inverse inertia tensor (relative to COM), shape [body_count, 3, 3], float
581
- body_mass (array): Rigid body mass, shape [body_count], float
582
- body_inv_mass (array): Rigid body inverse mass, shape [body_count], float
583
- body_name (list): Rigid body names, shape [body_count], str
584
-
585
- joint_q (array): Generalized joint positions used for state initialization, shape [joint_coord_count], float
586
- joint_qd (array): Generalized joint velocities used for state initialization, shape [joint_dof_count], float
587
- joint_act (array): Generalized joint control inputs, shape [joint_axis_count], float
588
- joint_type (array): Joint type, shape [joint_count], int
589
- joint_parent (array): Joint parent body indices, shape [joint_count], int
590
- joint_child (array): Joint child body indices, shape [joint_count], int
591
- joint_ancestor (array): Maps from joint index to the index of the joint that has the current joint parent body as child (-1 if no such joint ancestor exists), shape [joint_count], int
592
- joint_X_p (array): Joint transform in parent frame, shape [joint_count, 7], float
593
- joint_X_c (array): Joint mass frame in child frame, shape [joint_count, 7], float
594
- joint_axis (array): Joint axis in child frame, shape [joint_axis_count, 3], float
595
- joint_armature (array): Armature for each joint axis (only used by :class:`FeatherstoneIntegrator`), shape [joint_dof_count], float
596
- joint_target_ke (array): Joint stiffness, shape [joint_axis_count], float
597
- joint_target_kd (array): Joint damping, shape [joint_axis_count], float
598
- joint_axis_start (array): Start index of the first axis per joint, shape [joint_count], int
599
- joint_axis_dim (array): Number of linear and angular axes per joint, shape [joint_count, 2], int
600
- joint_axis_mode (array): Joint axis mode, shape [joint_axis_count], int. See `Joint modes`_.
601
- joint_linear_compliance (array): Joint linear compliance, shape [joint_count], float
602
- joint_angular_compliance (array): Joint linear compliance, shape [joint_count], float
603
- joint_enabled (array): Controls which joint is simulated (bodies become disconnected if False), shape [joint_count], int
604
-
605
- Note:
606
-
607
- This setting is not supported by :class:`FeatherstoneIntegrator`.
608
-
609
- joint_limit_lower (array): Joint lower position limits, shape [joint_axis_count], float
610
- joint_limit_upper (array): Joint upper position limits, shape [joint_axis_count], float
611
- joint_limit_ke (array): Joint position limit stiffness (used by the Euler integrators), shape [joint_axis_count], float
612
- joint_limit_kd (array): Joint position limit damping (used by the Euler integrators), shape [joint_axis_count], float
613
- joint_twist_lower (array): Joint lower twist limit, shape [joint_count], float
614
- joint_twist_upper (array): Joint upper twist limit, shape [joint_count], float
615
- joint_q_start (array): Start index of the first position coordinate per joint (note the last value is an additional sentinel entry to allow for querying the q dimensionality of joint i via ``joint_q_start[i+1] - joint_q_start[i]``), shape [joint_count + 1], int
616
- joint_qd_start (array): Start index of the first velocity coordinate per joint (note the last value is an additional sentinel entry to allow for querying the qd dimensionality of joint i via ``joint_qd_start[i+1] - joint_qd_start[i]``), shape [joint_count + 1], int
617
- articulation_start (array): Articulation start index, shape [articulation_count], int
618
- joint_name (list): Joint names, shape [joint_count], str
619
- joint_attach_ke (float): Joint attachment force stiffness (used by :class:`SemiImplicitIntegrator`)
620
- joint_attach_kd (float): Joint attachment force damping (used by :class:`SemiImplicitIntegrator`)
621
-
622
- soft_contact_radius (float): Contact radius used for self-collisions in the VBD integrator.
623
- soft_contact_margin (float): Contact margin for generation of soft contacts
624
- soft_contact_ke (float): Stiffness of soft contacts (used by the Euler integrators)
625
- soft_contact_kd (float): Damping of soft contacts (used by the Euler integrators)
626
- soft_contact_kf (float): Stiffness of friction force in soft contacts (used by the Euler integrators)
627
- soft_contact_mu (float): Friction coefficient of soft contacts
628
- soft_contact_restitution (float): Restitution coefficient of soft contacts (used by :class:`XPBDIntegrator`)
629
-
630
- soft_contact_count (array): Number of active particle-shape contacts, shape [1], int
631
- soft_contact_particle (array), Index of particle per soft contact point, shape [soft_contact_max], int
632
- soft_contact_shape (array), Index of shape per soft contact point, shape [soft_contact_max], int
633
- soft_contact_body_pos (array), Positional offset of soft contact point in body frame, shape [soft_contact_max], vec3
634
- soft_contact_body_vel (array), Linear velocity of soft contact point in body frame, shape [soft_contact_max], vec3
635
- soft_contact_normal (array), Contact surface normal of soft contact point in world space, shape [soft_contact_max], vec3
636
- soft_contact_tids (array), Thread indices of the soft contact points, shape [soft_contact_max], int
637
-
638
- rigid_contact_max (int): Maximum number of potential rigid body contact points to generate ignoring the `rigid_mesh_contact_max` limit.
639
- rigid_contact_max_limited (int): Maximum number of potential rigid body contact points to generate respecting the `rigid_mesh_contact_max` limit.
640
- rigid_mesh_contact_max (int): Maximum number of rigid body contact points to generate per mesh (0 = unlimited, default)
641
- rigid_contact_margin (float): Contact margin for generation of rigid body contacts
642
- rigid_contact_torsional_friction (float): Torsional friction coefficient for rigid body contacts (used by :class:`XPBDIntegrator`)
643
- rigid_contact_rolling_friction (float): Rolling friction coefficient for rigid body contacts (used by :class:`XPBDIntegrator`)
644
-
645
- rigid_contact_count (array): Number of active shape-shape contacts, shape [1], int
646
- rigid_contact_point0 (array): Contact point relative to frame of body 0, shape [rigid_contact_max], vec3
647
- rigid_contact_point1 (array): Contact point relative to frame of body 1, shape [rigid_contact_max], vec3
648
- rigid_contact_offset0 (array): Contact offset due to contact thickness relative to body 0, shape [rigid_contact_max], vec3
649
- rigid_contact_offset1 (array): Contact offset due to contact thickness relative to body 1, shape [rigid_contact_max], vec3
650
- rigid_contact_normal (array): Contact normal in world space, shape [rigid_contact_max], vec3
651
- rigid_contact_thickness (array): Total contact thickness, shape [rigid_contact_max], float
652
- rigid_contact_shape0 (array): Index of shape 0 per contact, shape [rigid_contact_max], int
653
- rigid_contact_shape1 (array): Index of shape 1 per contact, shape [rigid_contact_max], int
654
- rigid_contact_tids (array): Triangle indices of the contact points, shape [rigid_contact_max], int
655
- rigid_contact_pairwise_counter (array): Pairwise counter for contact generation, shape [rigid_contact_max], int
656
- rigid_contact_broad_shape0 (array): Broadphase shape index of shape 0 per contact, shape [rigid_contact_max], int
657
- rigid_contact_broad_shape1 (array): Broadphase shape index of shape 1 per contact, shape [rigid_contact_max], int
658
- rigid_contact_point_id (array): Contact point ID, shape [rigid_contact_max], int
659
- rigid_contact_point_limit (array): Contact point limit, shape [rigid_contact_max], int
660
-
661
- ground (bool): Whether the ground plane and ground contacts are enabled
662
- ground_plane (array): Ground plane 3D normal and offset, shape [4], float
663
- up_vector (np.ndarray): Up vector of the world, shape [3], float
664
- up_axis (int): Up axis, 0 for x, 1 for y, 2 for z
665
- gravity (np.ndarray): Gravity vector, shape [3], float
666
-
667
- particle_count (int): Total number of particles in the system
668
- body_count (int): Total number of bodies in the system
669
- shape_count (int): Total number of shapes in the system
670
- joint_count (int): Total number of joints in the system
671
- tri_count (int): Total number of triangles in the system
672
- tet_count (int): Total number of tetrahedra in the system
673
- edge_count (int): Total number of edges in the system
674
- spring_count (int): Total number of springs in the system
675
- contact_count (int): Total number of contacts in the system
676
- muscle_count (int): Total number of muscles in the system
677
- articulation_count (int): Total number of articulations in the system
678
- joint_dof_count (int): Total number of velocity degrees of freedom of all joints in the system
679
- joint_coord_count (int): Total number of position degrees of freedom of all joints in the system
680
-
681
- particle_color_groups (list of array): The coloring of all the particles, used for VBD's Gauss-Seidel iteration. Each array contains indices of particles sharing the same color.
682
- particle_colors (array): Contains the color assignment for every particle
683
-
684
- device (wp.Device): Device on which the Model was allocated
685
-
686
- Note:
687
- It is strongly recommended to use the ModelBuilder to construct a simulation rather
688
- than creating your own Model object directly, however it is possible to do so if
689
- desired.
690
- """
691
-
692
- def __init__(self, device=None):
693
- self.requires_grad = False
694
- self.num_envs = 0
695
-
696
- self.particle_q = None
697
- self.particle_qd = None
698
- self.particle_mass = None
699
- self.particle_inv_mass = None
700
- self.particle_radius = None
701
- self.particle_max_radius = 0.0
702
- self.particle_ke = 1.0e3
703
- self.particle_kd = 1.0e2
704
- self.particle_kf = 1.0e2
705
- self.particle_mu = 0.5
706
- self.particle_cohesion = 0.0
707
- self.particle_adhesion = 0.0
708
- self.particle_grid = None
709
- self.particle_flags = None
710
- self.particle_max_velocity = 1e5
711
-
712
- self.shape_transform = None
713
- self.shape_body = None
714
- self.shape_visible = None
715
- self.body_shapes = {}
716
- self.shape_materials = ModelShapeMaterials()
717
- self.shape_geo = ModelShapeGeometry()
718
- self.shape_geo_src = None
719
-
720
- self.shape_collision_group = None
721
- self.shape_collision_group_map = None
722
- self.shape_collision_filter_pairs = None
723
- self.shape_collision_radius = None
724
- self.shape_ground_collision = None
725
- self.shape_shape_collision = None
726
- self.shape_contact_pairs = None
727
- self.shape_ground_contact_pairs = None
728
-
729
- self.spring_indices = None
730
- self.spring_rest_length = None
731
- self.spring_stiffness = None
732
- self.spring_damping = None
733
- self.spring_control = None
734
- self.spring_constraint_lambdas = None
735
-
736
- self.tri_indices = None
737
- self.tri_poses = None
738
- self.tri_activations = None
739
- self.tri_materials = None
740
- self.tri_areas = None
741
-
742
- self.edge_indices = None
743
- self.edge_rest_angle = None
744
- self.edge_rest_length = None
745
- self.edge_bending_properties = None
746
- self.edge_constraint_lambdas = None
747
-
748
- self.tet_indices = None
749
- self.tet_poses = None
750
- self.tet_activations = None
751
- self.tet_materials = None
752
-
753
- self.muscle_start = None
754
- self.muscle_params = None
755
- self.muscle_bodies = None
756
- self.muscle_points = None
757
- self.muscle_activations = None
758
-
759
- self.body_q = None
760
- self.body_qd = None
761
- self.body_com = None
762
- self.body_inertia = None
763
- self.body_inv_inertia = None
764
- self.body_mass = None
765
- self.body_inv_mass = None
766
- self.body_name = None
767
-
768
- self.joint_q = None
769
- self.joint_qd = None
770
- self.joint_act = None
771
- self.joint_type = None
772
- self.joint_parent = None
773
- self.joint_child = None
774
- self.joint_ancestor = None
775
- self.joint_X_p = None
776
- self.joint_X_c = None
777
- self.joint_axis = None
778
- self.joint_armature = None
779
- self.joint_target_ke = None
780
- self.joint_target_kd = None
781
- self.joint_axis_start = None
782
- self.joint_axis_dim = None
783
- self.joint_axis_mode = None
784
- self.joint_linear_compliance = None
785
- self.joint_angular_compliance = None
786
- self.joint_enabled = None
787
- self.joint_limit_lower = None
788
- self.joint_limit_upper = None
789
- self.joint_limit_ke = None
790
- self.joint_limit_kd = None
791
- self.joint_twist_lower = None
792
- self.joint_twist_upper = None
793
- self.joint_q_start = None
794
- self.joint_qd_start = None
795
- self.articulation_start = None
796
- self.joint_name = None
797
-
798
- # todo: per-joint values?
799
- self.joint_attach_ke = 1.0e3
800
- self.joint_attach_kd = 1.0e2
801
-
802
- self.soft_contact_radius = 0.2
803
- self.soft_contact_margin = 0.2
804
- self.soft_contact_ke = 1.0e3
805
- self.soft_contact_kd = 10.0
806
- self.soft_contact_kf = 1.0e3
807
- self.soft_contact_mu = 0.5
808
- self.soft_contact_restitution = 0.0
809
-
810
- self.soft_contact_count = 0
811
- self.soft_contact_particle = None
812
- self.soft_contact_shape = None
813
- self.soft_contact_body_pos = None
814
- self.soft_contact_body_vel = None
815
- self.soft_contact_normal = None
816
- self.soft_contact_tids = None
817
-
818
- self.rigid_contact_max = 0
819
- self.rigid_contact_max_limited = 0
820
- self.rigid_mesh_contact_max = 0
821
- self.rigid_contact_margin = None
822
- self.rigid_contact_torsional_friction = None
823
- self.rigid_contact_rolling_friction = None
824
-
825
- self.rigid_contact_count = None
826
- self.rigid_contact_point0 = None
827
- self.rigid_contact_point1 = None
828
- self.rigid_contact_offset0 = None
829
- self.rigid_contact_offset1 = None
830
- self.rigid_contact_normal = None
831
- self.rigid_contact_thickness = None
832
- self.rigid_contact_shape0 = None
833
- self.rigid_contact_shape1 = None
834
- self.rigid_contact_tids = None
835
- self.rigid_contact_pairwise_counter = None
836
- self.rigid_contact_broad_shape0 = None
837
- self.rigid_contact_broad_shape1 = None
838
- self.rigid_contact_point_id = None
839
- self.rigid_contact_point_limit = None
840
-
841
- # toggles ground contact for all shapes
842
- self.ground = True
843
- self.ground_plane = None
844
- self.up_vector = np.array((0.0, 1.0, 0.0))
845
- self.up_axis = 1
846
- self.gravity = np.array((0.0, -9.80665, 0.0))
847
-
848
- self.particle_count = 0
849
- self.body_count = 0
850
- self.shape_count = 0
851
- self.joint_count = 0
852
- self.joint_axis_count = 0
853
- self.tri_count = 0
854
- self.tet_count = 0
855
- self.edge_count = 0
856
- self.spring_count = 0
857
- self.muscle_count = 0
858
- self.articulation_count = 0
859
- self.joint_dof_count = 0
860
- self.joint_coord_count = 0
861
-
862
- # indices of particles sharing the same color
863
- self.particle_color_groups = []
864
- # the color of each particles
865
- self.particle_colors = None
866
-
867
- self.device = wp.get_device(device)
868
-
869
- def state(self, requires_grad=None) -> State:
870
- """Returns a state object for the model
871
-
872
- The returned state will be initialized with the initial configuration given in
873
- the model description.
874
-
875
- Args:
876
- requires_grad (bool): Manual overwrite whether the state variables should have `requires_grad` enabled (defaults to `None` to use the model's setting :attr:`requires_grad`)
877
-
878
- Returns:
879
- State: The state object
880
- """
881
-
882
- s = State()
883
- if requires_grad is None:
884
- requires_grad = self.requires_grad
885
-
886
- # particles
887
- if self.particle_count:
888
- s.particle_q = wp.clone(self.particle_q, requires_grad=requires_grad)
889
- s.particle_qd = wp.clone(self.particle_qd, requires_grad=requires_grad)
890
- s.particle_f = wp.zeros_like(self.particle_qd, requires_grad=requires_grad)
891
-
892
- # articulations
893
- if self.body_count:
894
- s.body_q = wp.clone(self.body_q, requires_grad=requires_grad)
895
- s.body_qd = wp.clone(self.body_qd, requires_grad=requires_grad)
896
- s.body_f = wp.zeros_like(self.body_qd, requires_grad=requires_grad)
897
-
898
- if self.joint_count:
899
- s.joint_q = wp.clone(self.joint_q, requires_grad=requires_grad)
900
- s.joint_qd = wp.clone(self.joint_qd, requires_grad=requires_grad)
901
-
902
- return s
903
-
904
- def control(self, requires_grad=None, clone_variables=True) -> Control:
905
- """
906
- Returns a control object for the model.
907
-
908
- The returned control object will be initialized with the control inputs given in the model description.
909
-
910
- Args:
911
- requires_grad (bool): Manual overwrite whether the control variables should have `requires_grad` enabled (defaults to `None` to use the model's setting :attr:`requires_grad`)
912
- clone_variables (bool): Whether to clone the control inputs or use the original data
913
-
914
- Returns:
915
- Control: The control object
916
- """
917
- c = Control()
918
- if requires_grad is None:
919
- requires_grad = self.requires_grad
920
- if clone_variables:
921
- if self.joint_count:
922
- c.joint_act = wp.clone(self.joint_act, requires_grad=requires_grad)
923
- if self.tri_count:
924
- c.tri_activations = wp.clone(self.tri_activations, requires_grad=requires_grad)
925
- if self.tet_count:
926
- c.tet_activations = wp.clone(self.tet_activations, requires_grad=requires_grad)
927
- if self.muscle_count:
928
- c.muscle_activations = wp.clone(self.muscle_activations, requires_grad=requires_grad)
929
- else:
930
- c.joint_act = self.joint_act
931
- c.tri_activations = self.tri_activations
932
- c.tet_activations = self.tet_activations
933
- c.muscle_activations = self.muscle_activations
934
- return c
935
-
936
- def _allocate_soft_contacts(self, target, count, requires_grad=False):
937
- with wp.ScopedDevice(self.device):
938
- target.soft_contact_count = wp.zeros(1, dtype=wp.int32)
939
- target.soft_contact_particle = wp.zeros(count, dtype=int)
940
- target.soft_contact_shape = wp.zeros(count, dtype=int)
941
- target.soft_contact_body_pos = wp.zeros(count, dtype=wp.vec3, requires_grad=requires_grad)
942
- target.soft_contact_body_vel = wp.zeros(count, dtype=wp.vec3, requires_grad=requires_grad)
943
- target.soft_contact_normal = wp.zeros(count, dtype=wp.vec3, requires_grad=requires_grad)
944
- target.soft_contact_tids = wp.zeros(self.particle_count * (self.shape_count - 1), dtype=int)
945
-
946
- def allocate_soft_contacts(self, count, requires_grad=False):
947
- self._allocate_soft_contacts(self, count, requires_grad)
948
-
949
- def find_shape_contact_pairs(self):
950
- # find potential contact pairs based on collision groups and collision mask (pairwise filtering)
951
- import copy
952
- import itertools
953
-
954
- filters = copy.copy(self.shape_collision_filter_pairs)
955
- for a, b in self.shape_collision_filter_pairs:
956
- filters.add((b, a))
957
- contact_pairs = []
958
- # iterate over collision groups (islands)
959
- for group, shapes in self.shape_collision_group_map.items():
960
- for shape_a, shape_b in itertools.product(shapes, shapes):
961
- if not self.shape_shape_collision[shape_a]:
962
- continue
963
- if not self.shape_shape_collision[shape_b]:
964
- continue
965
- if shape_a < shape_b and (shape_a, shape_b) not in filters:
966
- contact_pairs.append((shape_a, shape_b))
967
- if group != -1 and -1 in self.shape_collision_group_map:
968
- # shapes with collision group -1 collide with all other shapes
969
- for shape_a, shape_b in itertools.product(shapes, self.shape_collision_group_map[-1]):
970
- if shape_a < shape_b and (shape_a, shape_b) not in filters:
971
- contact_pairs.append((shape_a, shape_b))
972
- self.shape_contact_pairs = wp.array(np.array(contact_pairs), dtype=wp.int32, device=self.device)
973
- self.shape_contact_pair_count = len(contact_pairs)
974
- # find ground contact pairs
975
- ground_contact_pairs = []
976
- ground_id = self.shape_count - 1
977
- for i in range(ground_id):
978
- if self.shape_ground_collision[i]:
979
- ground_contact_pairs.append((i, ground_id))
980
- self.shape_ground_contact_pairs = wp.array(np.array(ground_contact_pairs), dtype=wp.int32, device=self.device)
981
- self.shape_ground_contact_pair_count = len(ground_contact_pairs)
982
-
983
- def count_contact_points(self):
984
- """
985
- Counts the maximum number of rigid contact points that need to be allocated.
986
- This function returns two values corresponding to the maximum number of potential contacts
987
- excluding the limiting from `Model.rigid_mesh_contact_max` and the maximum number of
988
- contact points that may be generated when considering the `Model.rigid_mesh_contact_max` limit.
989
-
990
- :returns:
991
- - potential_count (int): Potential number of contact points
992
- - actual_count (int): Actual number of contact points
993
- """
994
- from .collide import count_contact_points
995
-
996
- # calculate the potential number of shape pair contact points
997
- contact_count = wp.zeros(2, dtype=wp.int32, device=self.device)
998
- wp.launch(
999
- kernel=count_contact_points,
1000
- dim=self.shape_contact_pair_count,
1001
- inputs=[
1002
- self.shape_contact_pairs,
1003
- self.shape_geo,
1004
- self.rigid_mesh_contact_max,
1005
- ],
1006
- outputs=[contact_count],
1007
- device=self.device,
1008
- record_tape=False,
1009
- )
1010
- # count ground contacts
1011
- wp.launch(
1012
- kernel=count_contact_points,
1013
- dim=self.shape_ground_contact_pair_count,
1014
- inputs=[
1015
- self.shape_ground_contact_pairs,
1016
- self.shape_geo,
1017
- self.rigid_mesh_contact_max,
1018
- ],
1019
- outputs=[contact_count],
1020
- device=self.device,
1021
- record_tape=False,
1022
- )
1023
- counts = contact_count.numpy()
1024
- potential_count = int(counts[0])
1025
- actual_count = int(counts[1])
1026
- return potential_count, actual_count
1027
-
1028
- def allocate_rigid_contacts(self, target=None, count=None, limited_contact_count=None, requires_grad=False):
1029
- if count is not None:
1030
- # potential number of contact points to consider
1031
- self.rigid_contact_max = count
1032
- if limited_contact_count is not None:
1033
- self.rigid_contact_max_limited = limited_contact_count
1034
- if target is None:
1035
- target = self
1036
-
1037
- with wp.ScopedDevice(self.device):
1038
- # serves as counter of the number of active contact points
1039
- target.rigid_contact_count = wp.zeros(1, dtype=wp.int32)
1040
- # contact point ID within the (shape_a, shape_b) contact pair
1041
- target.rigid_contact_point_id = wp.zeros(self.rigid_contact_max, dtype=wp.int32)
1042
- # position of contact point in body 0's frame before the integration step
1043
- target.rigid_contact_point0 = wp.zeros(
1044
- self.rigid_contact_max_limited, dtype=wp.vec3, requires_grad=requires_grad
1045
- )
1046
- # position of contact point in body 1's frame before the integration step
1047
- target.rigid_contact_point1 = wp.zeros(
1048
- self.rigid_contact_max_limited, dtype=wp.vec3, requires_grad=requires_grad
1049
- )
1050
- # moment arm before the integration step resulting from thickness displacement added to contact point 0 in body 0's frame (used in XPBD contact friction handling)
1051
- target.rigid_contact_offset0 = wp.zeros(
1052
- self.rigid_contact_max_limited, dtype=wp.vec3, requires_grad=requires_grad
1053
- )
1054
- # moment arm before the integration step resulting from thickness displacement added to contact point 1 in body 1's frame (used in XPBD contact friction handling)
1055
- target.rigid_contact_offset1 = wp.zeros(
1056
- self.rigid_contact_max_limited, dtype=wp.vec3, requires_grad=requires_grad
1057
- )
1058
- # contact normal in world frame
1059
- target.rigid_contact_normal = wp.zeros(
1060
- self.rigid_contact_max_limited, dtype=wp.vec3, requires_grad=requires_grad
1061
- )
1062
- # combined thickness of both shapes
1063
- target.rigid_contact_thickness = wp.zeros(
1064
- self.rigid_contact_max_limited, dtype=wp.float32, requires_grad=requires_grad
1065
- )
1066
- # ID of the first shape in the contact pair
1067
- target.rigid_contact_shape0 = wp.zeros(self.rigid_contact_max_limited, dtype=wp.int32)
1068
- # ID of the second shape in the contact pair
1069
- target.rigid_contact_shape1 = wp.zeros(self.rigid_contact_max_limited, dtype=wp.int32)
1070
-
1071
- # shape IDs of potential contact pairs found during broadphase
1072
- target.rigid_contact_broad_shape0 = wp.zeros(self.rigid_contact_max, dtype=wp.int32)
1073
- target.rigid_contact_broad_shape1 = wp.zeros(self.rigid_contact_max, dtype=wp.int32)
1074
-
1075
- if self.rigid_mesh_contact_max > 0:
1076
- # add additional buffers to track how many contact points are generated per contact pair
1077
- # (significantly increases memory usage, only enable if mesh contacts need to be pruned)
1078
- if self.shape_count >= 46340:
1079
- # clip the number of potential contacts to avoid signed 32-bit integer overflow
1080
- # i.e. when the number of shapes exceeds sqrt(2**31 - 1)
1081
- max_pair_count = 2**31 - 1
1082
- else:
1083
- max_pair_count = self.shape_count * self.shape_count
1084
- # maximum number of contact points per contact pair
1085
- target.rigid_contact_point_limit = wp.zeros(max_pair_count, dtype=wp.int32)
1086
- # currently found contacts per contact pair
1087
- target.rigid_contact_pairwise_counter = wp.zeros(max_pair_count, dtype=wp.int32)
1088
- else:
1089
- target.rigid_contact_point_limit = None
1090
- target.rigid_contact_pairwise_counter = None
1091
-
1092
- # ID of thread that found the current contact point
1093
- target.rigid_contact_tids = wp.zeros(self.rigid_contact_max, dtype=wp.int32)
1094
-
1095
- @property
1096
- def soft_contact_max(self):
1097
- """Maximum number of soft contacts that can be registered"""
1098
- if self.soft_contact_particle is None:
1099
- return 0
1100
- return len(self.soft_contact_particle)
1101
-
1102
-
1103
- class ModelBuilder:
1104
- """A helper class for building simulation models at runtime.
1105
-
1106
- Use the ModelBuilder to construct a simulation scene. The ModelBuilder
1107
- and builds the scene representation using standard Python data structures (lists),
1108
- this means it is not differentiable. Once :func:`finalize()`
1109
- has been called the ModelBuilder transfers all data to Warp tensors and returns
1110
- an object that may be used for simulation.
1111
-
1112
- Example
1113
- -------
1114
-
1115
- .. code-block:: python
1116
-
1117
- import warp as wp
1118
- import warp.sim
1119
-
1120
- builder = wp.sim.ModelBuilder()
1121
-
1122
- # anchor point (zero mass)
1123
- builder.add_particle((0, 1.0, 0.0), (0.0, 0.0, 0.0), 0.0)
1124
-
1125
- # build chain
1126
- for i in range(1, 10):
1127
- builder.add_particle((i, 1.0, 0.0), (0.0, 0.0, 0.0), 1.0)
1128
- builder.add_spring(i - 1, i, 1.0e3, 0.0, 0)
1129
-
1130
- # create model
1131
- model = builder.finalize("cuda")
1132
-
1133
- state = model.state()
1134
- control = model.control() # optional, to support time-varying control inputs
1135
- integrator = wp.sim.SemiImplicitIntegrator()
1136
-
1137
- for i in range(100):
1138
- state.clear_forces()
1139
- integrator.simulate(model, state, state, dt=1.0 / 60.0, control=control)
1140
-
1141
- Note:
1142
- It is strongly recommended to use the ModelBuilder to construct a simulation rather
1143
- than creating your own Model object directly, however it is possible to do so if
1144
- desired.
1145
- """
1146
-
1147
- # Default particle settings
1148
- default_particle_radius = 0.1
1149
-
1150
- # Default triangle soft mesh settings
1151
- default_tri_ke = 100.0
1152
- default_tri_ka = 100.0
1153
- default_tri_kd = 10.0
1154
- default_tri_drag = 0.0
1155
- default_tri_lift = 0.0
1156
-
1157
- # Default distance constraint properties
1158
- default_spring_ke = 100.0
1159
- default_spring_kd = 0.0
1160
-
1161
- # Default edge bending properties
1162
- default_edge_ke = 100.0
1163
- default_edge_kd = 0.0
1164
-
1165
- # Default rigid shape contact material properties
1166
- default_shape_ke = 1.0e5
1167
- default_shape_kd = 1000.0
1168
- default_shape_kf = 1000.0
1169
- default_shape_ka = 0.0
1170
- default_shape_mu = 0.5
1171
- default_shape_restitution = 0.0
1172
- default_shape_density = 1000.0
1173
- default_shape_thickness = 1e-5
1174
-
1175
- # Default joint settings
1176
- default_joint_limit_ke = 100.0
1177
- default_joint_limit_kd = 1.0
1178
-
1179
- def __init__(self, up_vector=(0.0, 1.0, 0.0), gravity=-9.80665):
1180
- self.num_envs = 0
1181
-
1182
- # particles
1183
- self.particle_q = []
1184
- self.particle_qd = []
1185
- self.particle_mass = []
1186
- self.particle_radius = []
1187
- self.particle_flags = []
1188
- self.particle_max_velocity = 1e5
1189
- # list of np.array
1190
- self.particle_color_groups = []
1191
-
1192
- # shapes (each shape has an entry in these arrays)
1193
- # transform from shape to body
1194
- self.shape_transform = []
1195
- # maps from shape index to body index
1196
- self.shape_body = []
1197
- self.shape_visible = []
1198
- self.shape_geo_type = []
1199
- self.shape_geo_scale = []
1200
- self.shape_geo_src = []
1201
- self.shape_geo_is_solid = []
1202
- self.shape_geo_thickness = []
1203
- self.shape_material_ke = []
1204
- self.shape_material_kd = []
1205
- self.shape_material_kf = []
1206
- self.shape_material_ka = []
1207
- self.shape_material_mu = []
1208
- self.shape_material_restitution = []
1209
- # collision groups within collisions are handled
1210
- self.shape_collision_group = []
1211
- self.shape_collision_group_map = {}
1212
- self.last_collision_group = 0
1213
- # radius to use for broadphase collision checking
1214
- self.shape_collision_radius = []
1215
- # whether the shape collides with the ground
1216
- self.shape_ground_collision = []
1217
- # whether the shape collides with any other shape
1218
- self.shape_shape_collision = []
1219
-
1220
- # filtering to ignore certain collision pairs
1221
- self.shape_collision_filter_pairs = set()
1222
-
1223
- # geometry
1224
- self.geo_meshes = []
1225
- self.geo_sdfs = []
1226
-
1227
- # springs
1228
- self.spring_indices = []
1229
- self.spring_rest_length = []
1230
- self.spring_stiffness = []
1231
- self.spring_damping = []
1232
- self.spring_control = []
1233
-
1234
- # triangles
1235
- self.tri_indices = []
1236
- self.tri_poses = []
1237
- self.tri_activations = []
1238
- self.tri_materials = []
1239
- self.tri_areas = []
1240
-
1241
- # edges (bending)
1242
- self.edge_indices = []
1243
- self.edge_rest_angle = []
1244
- self.edge_rest_length = []
1245
- self.edge_bending_properties = []
1246
-
1247
- # tetrahedra
1248
- self.tet_indices = []
1249
- self.tet_poses = []
1250
- self.tet_activations = []
1251
- self.tet_materials = []
1252
-
1253
- # muscles
1254
- self.muscle_start = []
1255
- self.muscle_params = []
1256
- self.muscle_activations = []
1257
- self.muscle_bodies = []
1258
- self.muscle_points = []
1259
-
1260
- # rigid bodies
1261
- self.body_mass = []
1262
- self.body_inertia = []
1263
- self.body_inv_mass = []
1264
- self.body_inv_inertia = []
1265
- self.body_com = []
1266
- self.body_q = []
1267
- self.body_qd = []
1268
- self.body_name = []
1269
- self.body_shapes = {} # mapping from body to shapes
1270
-
1271
- # rigid joints
1272
- self.joint_parent = [] # index of the parent body (constant)
1273
- self.joint_parents = {} # mapping from joint to parent bodies
1274
- self.joint_child = [] # index of the child body (constant)
1275
- self.joint_axis = [] # joint axis in child joint frame (constant)
1276
- self.joint_X_p = [] # frame of joint in parent (constant)
1277
- self.joint_X_c = [] # frame of child com (in child coordinates) (constant)
1278
- self.joint_q = []
1279
- self.joint_qd = []
1280
-
1281
- self.joint_type = []
1282
- self.joint_name = []
1283
- self.joint_armature = []
1284
- self.joint_target_ke = []
1285
- self.joint_target_kd = []
1286
- self.joint_axis_mode = []
1287
- self.joint_limit_lower = []
1288
- self.joint_limit_upper = []
1289
- self.joint_limit_ke = []
1290
- self.joint_limit_kd = []
1291
- self.joint_act = []
1292
-
1293
- self.joint_twist_lower = []
1294
- self.joint_twist_upper = []
1295
-
1296
- self.joint_linear_compliance = []
1297
- self.joint_angular_compliance = []
1298
- self.joint_enabled = []
1299
-
1300
- self.joint_q_start = []
1301
- self.joint_qd_start = []
1302
- self.joint_axis_start = []
1303
- self.joint_axis_dim = []
1304
- self.articulation_start = []
1305
-
1306
- self.joint_dof_count = 0
1307
- self.joint_coord_count = 0
1308
- self.joint_axis_total_count = 0
1309
-
1310
- self.up_vector = wp.vec3(up_vector)
1311
- self.up_axis = int(np.argmax(np.abs(up_vector)))
1312
- self.gravity = gravity
1313
- # indicates whether a ground plane has been created
1314
- self._ground_created = False
1315
- # constructor parameters for ground plane shape
1316
- self._ground_params = {
1317
- "plane": (*up_vector, 0.0),
1318
- "width": 0.0,
1319
- "length": 0.0,
1320
- "ke": self.default_shape_ke,
1321
- "kd": self.default_shape_kd,
1322
- "kf": self.default_shape_kf,
1323
- "mu": self.default_shape_mu,
1324
- "restitution": self.default_shape_restitution,
1325
- }
1326
-
1327
- # Maximum number of soft contacts that can be registered
1328
- self.soft_contact_max = 64 * 1024
1329
-
1330
- # maximum number of contact points to generate per mesh shape
1331
- self.rigid_mesh_contact_max = 0 # 0 = unlimited
1332
-
1333
- # contacts to be generated within the given distance margin to be generated at
1334
- # every simulation substep (can be 0 if only one PBD solver iteration is used)
1335
- self.rigid_contact_margin = 0.1
1336
- # torsional friction coefficient (only considered by XPBD so far)
1337
- self.rigid_contact_torsional_friction = 0.5
1338
- # rolling friction coefficient (only considered by XPBD so far)
1339
- self.rigid_contact_rolling_friction = 0.001
1340
-
1341
- # number of rigid contact points to allocate in the model during self.finalize() per environment
1342
- # if setting is None, the number of worst-case number of contacts will be calculated in self.finalize()
1343
- self.num_rigid_contacts_per_env = None
1344
-
1345
- @property
1346
- def shape_count(self):
1347
- return len(self.shape_geo_type)
1348
-
1349
- @property
1350
- def body_count(self):
1351
- return len(self.body_q)
1352
-
1353
- @property
1354
- def joint_count(self):
1355
- return len(self.joint_type)
1356
-
1357
- @property
1358
- def joint_axis_count(self):
1359
- return len(self.joint_axis)
1360
-
1361
- @property
1362
- def particle_count(self):
1363
- return len(self.particle_q)
1364
-
1365
- @property
1366
- def tri_count(self):
1367
- return len(self.tri_poses)
1368
-
1369
- @property
1370
- def tet_count(self):
1371
- return len(self.tet_poses)
1372
-
1373
- @property
1374
- def edge_count(self):
1375
- return len(self.edge_rest_angle)
1376
-
1377
- @property
1378
- def spring_count(self):
1379
- return len(self.spring_rest_length)
1380
-
1381
- @property
1382
- def muscle_count(self):
1383
- return len(self.muscle_start)
1384
-
1385
- @property
1386
- def articulation_count(self):
1387
- return len(self.articulation_start)
1388
-
1389
- # an articulation is a set of contiguous bodies bodies from articulation_start[i] to articulation_start[i+1]
1390
- # these are used for computing forward kinematics e.g.:
1391
- #
1392
- # model.eval_articulation_fk()
1393
- # model.eval_articulation_j()
1394
- # model.eval_articulation_m()
1395
- #
1396
- # articulations are automatically 'closed' when calling finalize
1397
-
1398
- def add_articulation(self):
1399
- self.articulation_start.append(self.joint_count)
1400
-
1401
- def add_builder(
1402
- self,
1403
- builder: ModelBuilder,
1404
- xform: Transform | None = None,
1405
- update_num_env_count: bool = True,
1406
- separate_collision_group: bool = True,
1407
- ):
1408
- """Copies the data from `builder`, another `ModelBuilder` to this `ModelBuilder`.
1409
-
1410
- Args:
1411
- builder (ModelBuilder): a model builder to add model data from.
1412
- xform (:ref:`transform <transform>`): offset transform applied to root bodies.
1413
- update_num_env_count (bool): if True, the number of environments is incremented by 1.
1414
- separate_collision_group (bool): if True, the shapes from the articulations in `builder` will all be put into a single new collision group, otherwise, only the shapes in collision group > -1 will be moved to a new group.
1415
- """
1416
-
1417
- start_particle_idx = self.particle_count
1418
- if builder.particle_count:
1419
- self.particle_max_velocity = builder.particle_max_velocity
1420
- if xform is not None:
1421
- pos_offset = wp.transform_get_translation(xform)
1422
- else:
1423
- pos_offset = np.zeros(3)
1424
- self.particle_q.extend((np.array(builder.particle_q) + pos_offset).tolist())
1425
- # other particle attributes are added below
1426
-
1427
- if builder.spring_count:
1428
- self.spring_indices.extend((np.array(builder.spring_indices, dtype=np.int32) + start_particle_idx).tolist())
1429
- if builder.edge_count:
1430
- # Update edge indices by adding offset, preserving -1 values
1431
- edge_indices = np.array(builder.edge_indices, dtype=np.int32)
1432
- mask = edge_indices != -1
1433
- edge_indices[mask] += start_particle_idx
1434
- self.edge_indices.extend(edge_indices.tolist())
1435
- if builder.tri_count:
1436
- self.tri_indices.extend((np.array(builder.tri_indices, dtype=np.int32) + start_particle_idx).tolist())
1437
- if builder.tet_count:
1438
- self.tet_indices.extend((np.array(builder.tet_indices, dtype=np.int32) + start_particle_idx).tolist())
1439
-
1440
- builder_coloring_translated = [group + start_particle_idx for group in builder.particle_color_groups]
1441
- self.particle_color_groups = combine_independent_particle_coloring(
1442
- self.particle_color_groups, builder_coloring_translated
1443
- )
1444
-
1445
- start_body_idx = self.body_count
1446
- start_shape_idx = self.shape_count
1447
- for s, b in enumerate(builder.shape_body):
1448
- if b > -1:
1449
- new_b = b + start_body_idx
1450
- self.shape_body.append(new_b)
1451
- self.shape_transform.append(builder.shape_transform[s])
1452
- else:
1453
- self.shape_body.append(-1)
1454
- # apply offset transform to root bodies
1455
- if xform is not None:
1456
- self.shape_transform.append(xform * wp.transform(*builder.shape_transform[s]))
1457
- else:
1458
- self.shape_transform.append(builder.shape_transform[s])
1459
-
1460
- for b, shapes in builder.body_shapes.items():
1461
- self.body_shapes[b + start_body_idx] = [s + start_shape_idx for s in shapes]
1462
-
1463
- if builder.joint_count:
1464
- joint_X_p = copy.deepcopy(builder.joint_X_p)
1465
- joint_q = copy.deepcopy(builder.joint_q)
1466
- if xform is not None:
1467
- for i in range(len(joint_X_p)):
1468
- if builder.joint_type[i] == wp.sim.JOINT_FREE:
1469
- qi = builder.joint_q_start[i]
1470
- xform_prev = wp.transform(joint_q[qi : qi + 3], joint_q[qi + 3 : qi + 7])
1471
- tf = xform * xform_prev
1472
- joint_q[qi : qi + 3] = tf.p
1473
- joint_q[qi + 3 : qi + 7] = tf.q
1474
- elif builder.joint_parent[i] == -1:
1475
- joint_X_p[i] = xform * wp.transform(*joint_X_p[i])
1476
- self.joint_X_p.extend(joint_X_p)
1477
- self.joint_q.extend(joint_q)
1478
-
1479
- # offset the indices
1480
- self.articulation_start.extend([a + self.joint_count for a in builder.articulation_start])
1481
- self.joint_parent.extend([p + self.body_count if p != -1 else -1 for p in builder.joint_parent])
1482
- self.joint_child.extend([c + self.body_count for c in builder.joint_child])
1483
-
1484
- self.joint_q_start.extend([c + self.joint_coord_count for c in builder.joint_q_start])
1485
- self.joint_qd_start.extend([c + self.joint_dof_count for c in builder.joint_qd_start])
1486
-
1487
- self.joint_axis_start.extend([a + self.joint_axis_total_count for a in builder.joint_axis_start])
1488
-
1489
- for i in range(builder.body_count):
1490
- if xform is not None:
1491
- self.body_q.append(xform * wp.transform(*builder.body_q[i]))
1492
- else:
1493
- self.body_q.append(builder.body_q[i])
1494
-
1495
- # apply collision group
1496
- if separate_collision_group:
1497
- self.shape_collision_group.extend([self.last_collision_group + 1 for _ in builder.shape_collision_group])
1498
- else:
1499
- self.shape_collision_group.extend(
1500
- [(g + self.last_collision_group if g > -1 else -1) for g in builder.shape_collision_group]
1501
- )
1502
- shape_count = self.shape_count
1503
- for i, j in builder.shape_collision_filter_pairs:
1504
- self.shape_collision_filter_pairs.add((i + shape_count, j + shape_count))
1505
- for group, shapes in builder.shape_collision_group_map.items():
1506
- if separate_collision_group:
1507
- extend_group = self.last_collision_group + 1
1508
- else:
1509
- extend_group = group + self.last_collision_group if group > -1 else -1
1510
-
1511
- if extend_group not in self.shape_collision_group_map:
1512
- self.shape_collision_group_map[extend_group] = []
1513
-
1514
- self.shape_collision_group_map[extend_group].extend([s + shape_count for s in shapes])
1515
-
1516
- # update last collision group counter
1517
- if separate_collision_group:
1518
- self.last_collision_group += 1
1519
- elif builder.last_collision_group > -1:
1520
- self.last_collision_group += builder.last_collision_group
1521
-
1522
- more_builder_attrs = [
1523
- "body_inertia",
1524
- "body_mass",
1525
- "body_inv_inertia",
1526
- "body_inv_mass",
1527
- "body_com",
1528
- "body_qd",
1529
- "body_name",
1530
- "joint_type",
1531
- "joint_enabled",
1532
- "joint_X_c",
1533
- "joint_armature",
1534
- "joint_axis",
1535
- "joint_axis_dim",
1536
- "joint_axis_mode",
1537
- "joint_name",
1538
- "joint_qd",
1539
- "joint_act",
1540
- "joint_limit_lower",
1541
- "joint_limit_upper",
1542
- "joint_limit_ke",
1543
- "joint_limit_kd",
1544
- "joint_target_ke",
1545
- "joint_target_kd",
1546
- "joint_linear_compliance",
1547
- "joint_angular_compliance",
1548
- "shape_visible",
1549
- "shape_geo_type",
1550
- "shape_geo_scale",
1551
- "shape_geo_src",
1552
- "shape_geo_is_solid",
1553
- "shape_geo_thickness",
1554
- "shape_material_ke",
1555
- "shape_material_kd",
1556
- "shape_material_kf",
1557
- "shape_material_ka",
1558
- "shape_material_mu",
1559
- "shape_material_restitution",
1560
- "shape_collision_radius",
1561
- "shape_ground_collision",
1562
- "shape_shape_collision",
1563
- "particle_qd",
1564
- "particle_mass",
1565
- "particle_radius",
1566
- "particle_flags",
1567
- "edge_rest_angle",
1568
- "edge_rest_length",
1569
- "edge_bending_properties",
1570
- "spring_rest_length",
1571
- "spring_stiffness",
1572
- "spring_damping",
1573
- "spring_control",
1574
- "tri_poses",
1575
- "tri_activations",
1576
- "tri_materials",
1577
- "tri_areas",
1578
- "tet_poses",
1579
- "tet_activations",
1580
- "tet_materials",
1581
- ]
1582
-
1583
- for attr in more_builder_attrs:
1584
- getattr(self, attr).extend(getattr(builder, attr))
1585
-
1586
- self.joint_dof_count += builder.joint_dof_count
1587
- self.joint_coord_count += builder.joint_coord_count
1588
- self.joint_axis_total_count += builder.joint_axis_total_count
1589
-
1590
- self.up_vector = builder.up_vector
1591
- self.gravity = builder.gravity
1592
- self._ground_params = builder._ground_params
1593
-
1594
- if update_num_env_count:
1595
- self.num_envs += 1
1596
-
1597
- # register a rigid body and return its index.
1598
- def add_body(
1599
- self,
1600
- origin: Transform | None = None,
1601
- armature: float = 0.0,
1602
- com: Vec3 | None = None,
1603
- I_m: Mat33 | None = None,
1604
- m: float = 0.0,
1605
- name: str | None = None,
1606
- ) -> int:
1607
- """Adds a rigid body to the model.
1608
-
1609
- Args:
1610
- origin: The location of the body in the world frame
1611
- armature: Artificial inertia added to the body
1612
- com: The center of mass of the body w.r.t its origin
1613
- I_m: The 3x3 inertia tensor of the body (specified relative to the center of mass)
1614
- m: Mass of the body
1615
- name: Name of the body (optional)
1616
-
1617
- Returns:
1618
- The index of the body in the model
1619
-
1620
- Note:
1621
- If the mass (m) is zero then the body is treated as kinematic with no dynamics
1622
-
1623
- """
1624
-
1625
- if origin is None:
1626
- origin = wp.transform()
1627
-
1628
- if com is None:
1629
- com = wp.vec3()
1630
-
1631
- if I_m is None:
1632
- I_m = wp.mat33()
1633
-
1634
- body_id = len(self.body_mass)
1635
-
1636
- # body data
1637
- inertia = I_m + wp.mat33(np.eye(3)) * armature
1638
- self.body_inertia.append(inertia)
1639
- self.body_mass.append(m)
1640
- self.body_com.append(com)
1641
-
1642
- if m > 0.0:
1643
- self.body_inv_mass.append(1.0 / m)
1644
- else:
1645
- self.body_inv_mass.append(0.0)
1646
-
1647
- if any(x for x in inertia):
1648
- self.body_inv_inertia.append(wp.inverse(inertia))
1649
- else:
1650
- self.body_inv_inertia.append(inertia)
1651
-
1652
- self.body_q.append(origin)
1653
- self.body_qd.append(wp.spatial_vector())
1654
-
1655
- self.body_name.append(name or f"body {body_id}")
1656
- self.body_shapes[body_id] = []
1657
- return body_id
1658
-
1659
- def add_joint(
1660
- self,
1661
- joint_type: wp.constant,
1662
- parent: int,
1663
- child: int,
1664
- linear_axes: list[JointAxis] | None = None,
1665
- angular_axes: list[JointAxis] | None = None,
1666
- name: str | None = None,
1667
- parent_xform: wp.transform | None = None,
1668
- child_xform: wp.transform | None = None,
1669
- linear_compliance: float = 0.0,
1670
- angular_compliance: float = 0.0,
1671
- armature: float = 1e-2,
1672
- collision_filter_parent: bool = True,
1673
- enabled: bool = True,
1674
- ) -> int:
1675
- """
1676
- Generic method to add any type of joint to this ModelBuilder.
1677
-
1678
- Args:
1679
- joint_type (constant): The type of joint to add (see `Joint types`_)
1680
- parent (int): The index of the parent body (-1 is the world)
1681
- child (int): The index of the child body
1682
- linear_axes (list(:class:`JointAxis`)): The linear axes (see :class:`JointAxis`) of the joint
1683
- angular_axes (list(:class:`JointAxis`)): The angular axes (see :class:`JointAxis`) of the joint
1684
- name (str): The name of the joint (optional)
1685
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
1686
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
1687
- linear_compliance (float): The linear compliance of the joint
1688
- angular_compliance (float): The angular compliance of the joint
1689
- armature (float): Artificial inertia added around the joint axes (only considered by :class:`FeatherstoneIntegrator`)
1690
- collision_filter_parent (bool): Whether to filter collisions between shapes of the parent and child bodies
1691
- enabled (bool): Whether the joint is enabled (not considered by :class:`FeatherstoneIntegrator`)
1692
-
1693
- Returns:
1694
- The index of the added joint
1695
- """
1696
- if linear_axes is None:
1697
- linear_axes = []
1698
-
1699
- if angular_axes is None:
1700
- angular_axes = []
1701
-
1702
- if parent_xform is None:
1703
- parent_xform = wp.transform()
1704
-
1705
- if child_xform is None:
1706
- child_xform = wp.transform()
1707
-
1708
- if len(self.articulation_start) == 0:
1709
- # automatically add an articulation if none exists
1710
- self.add_articulation()
1711
- self.joint_type.append(joint_type)
1712
- self.joint_parent.append(parent)
1713
- if child not in self.joint_parents:
1714
- self.joint_parents[child] = [parent]
1715
- else:
1716
- self.joint_parents[child].append(parent)
1717
- self.joint_child.append(child)
1718
- self.joint_X_p.append(wp.transform(parent_xform))
1719
- self.joint_X_c.append(wp.transform(child_xform))
1720
- self.joint_name.append(name or f"joint {self.joint_count}")
1721
- self.joint_axis_start.append(len(self.joint_axis))
1722
- self.joint_axis_dim.append((len(linear_axes), len(angular_axes)))
1723
- self.joint_axis_total_count += len(linear_axes) + len(angular_axes)
1724
-
1725
- self.joint_linear_compliance.append(linear_compliance)
1726
- self.joint_angular_compliance.append(angular_compliance)
1727
- self.joint_enabled.append(enabled)
1728
-
1729
- def add_axis_dim(dim: JointAxis):
1730
- self.joint_axis.append(dim.axis)
1731
- self.joint_axis_mode.append(dim.mode)
1732
- self.joint_act.append(dim.action)
1733
- self.joint_target_ke.append(dim.target_ke)
1734
- self.joint_target_kd.append(dim.target_kd)
1735
- self.joint_limit_ke.append(dim.limit_ke)
1736
- self.joint_limit_kd.append(dim.limit_kd)
1737
- if np.isfinite(dim.limit_lower):
1738
- self.joint_limit_lower.append(dim.limit_lower)
1739
- else:
1740
- self.joint_limit_lower.append(-1e6)
1741
- if np.isfinite(dim.limit_upper):
1742
- self.joint_limit_upper.append(dim.limit_upper)
1743
- else:
1744
- self.joint_limit_upper.append(1e6)
1745
-
1746
- for dim in linear_axes:
1747
- add_axis_dim(dim)
1748
- for dim in angular_axes:
1749
- add_axis_dim(dim)
1750
-
1751
- if joint_type == JOINT_PRISMATIC:
1752
- dof_count = 1
1753
- coord_count = 1
1754
- elif joint_type == JOINT_REVOLUTE:
1755
- dof_count = 1
1756
- coord_count = 1
1757
- elif joint_type == JOINT_BALL:
1758
- dof_count = 3
1759
- coord_count = 4
1760
- elif joint_type == JOINT_FREE or joint_type == JOINT_DISTANCE:
1761
- dof_count = 6
1762
- coord_count = 7
1763
- elif joint_type == JOINT_FIXED:
1764
- dof_count = 0
1765
- coord_count = 0
1766
- elif joint_type == JOINT_UNIVERSAL:
1767
- dof_count = 2
1768
- coord_count = 2
1769
- elif joint_type == JOINT_COMPOUND:
1770
- dof_count = 3
1771
- coord_count = 3
1772
- elif joint_type == JOINT_D6:
1773
- dof_count = len(linear_axes) + len(angular_axes)
1774
- coord_count = dof_count
1775
-
1776
- for _i in range(coord_count):
1777
- self.joint_q.append(0.0)
1778
-
1779
- for _i in range(dof_count):
1780
- self.joint_qd.append(0.0)
1781
- self.joint_armature.append(armature)
1782
-
1783
- if joint_type == JOINT_FREE or joint_type == JOINT_DISTANCE or joint_type == JOINT_BALL:
1784
- # ensure that a valid quaternion is used for the angular dofs
1785
- self.joint_q[-1] = 1.0
1786
-
1787
- self.joint_q_start.append(self.joint_coord_count)
1788
- self.joint_qd_start.append(self.joint_dof_count)
1789
-
1790
- self.joint_dof_count += dof_count
1791
- self.joint_coord_count += coord_count
1792
-
1793
- if collision_filter_parent and parent > -1:
1794
- for child_shape in self.body_shapes[child]:
1795
- for parent_shape in self.body_shapes[parent]:
1796
- self.shape_collision_filter_pairs.add((parent_shape, child_shape))
1797
-
1798
- return self.joint_count - 1
1799
-
1800
- def add_joint_revolute(
1801
- self,
1802
- parent: int,
1803
- child: int,
1804
- parent_xform: wp.transform | None = None,
1805
- child_xform: wp.transform | None = None,
1806
- axis: Vec3 = (1.0, 0.0, 0.0),
1807
- target: float | None = None,
1808
- target_ke: float = 0.0,
1809
- target_kd: float = 0.0,
1810
- mode: int = JOINT_MODE_FORCE,
1811
- limit_lower: float = -2 * math.pi,
1812
- limit_upper: float = 2 * math.pi,
1813
- limit_ke: float | None = None,
1814
- limit_kd: float | None = None,
1815
- linear_compliance: float = 0.0,
1816
- angular_compliance: float = 0.0,
1817
- armature: float = 1e-2,
1818
- name: str | None = None,
1819
- collision_filter_parent: bool = True,
1820
- enabled: bool = True,
1821
- ) -> int:
1822
- """Adds a revolute (hinge) joint to the model. It has one degree of freedom.
1823
-
1824
- Args:
1825
- parent: The index of the parent body
1826
- child: The index of the child body
1827
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
1828
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
1829
- axis (3D vector or JointAxis): The axis of rotation in the parent body's local frame, can be a JointAxis object whose settings will be used instead of the other arguments
1830
- target: The target angle (in radians) or target velocity of the joint (if None, the joint is considered to be in force control mode)
1831
- target_ke: The stiffness of the joint target
1832
- target_kd: The damping of the joint target
1833
- limit_lower: The lower limit of the joint
1834
- limit_upper: The upper limit of the joint
1835
- limit_ke: The stiffness of the joint limit (None to use the default value :attr:`default_joint_limit_ke`)
1836
- limit_kd: The damping of the joint limit (None to use the default value :attr:`default_joint_limit_kd`)
1837
- linear_compliance: The linear compliance of the joint
1838
- angular_compliance: The angular compliance of the joint
1839
- armature: Artificial inertia added around the joint axis
1840
- name: The name of the joint
1841
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
1842
- enabled: Whether the joint is enabled
1843
-
1844
- Returns:
1845
- The index of the added joint
1846
-
1847
- """
1848
- if parent_xform is None:
1849
- parent_xform = wp.transform()
1850
-
1851
- if child_xform is None:
1852
- child_xform = wp.transform()
1853
-
1854
- limit_ke = limit_ke if limit_ke is not None else self.default_joint_limit_ke
1855
- limit_kd = limit_kd if limit_kd is not None else self.default_joint_limit_kd
1856
-
1857
- action = 0.0
1858
- if target is None and mode == JOINT_MODE_TARGET_POSITION:
1859
- action = 0.5 * (limit_lower + limit_upper)
1860
- elif target is not None:
1861
- action = target
1862
- if mode == JOINT_MODE_FORCE:
1863
- mode = JOINT_MODE_TARGET_POSITION
1864
- ax = JointAxis(
1865
- axis=axis,
1866
- limit_lower=limit_lower,
1867
- limit_upper=limit_upper,
1868
- action=action,
1869
- target_ke=target_ke,
1870
- target_kd=target_kd,
1871
- mode=mode,
1872
- limit_ke=limit_ke,
1873
- limit_kd=limit_kd,
1874
- )
1875
- return self.add_joint(
1876
- JOINT_REVOLUTE,
1877
- parent,
1878
- child,
1879
- parent_xform=parent_xform,
1880
- child_xform=child_xform,
1881
- angular_axes=[ax],
1882
- linear_compliance=linear_compliance,
1883
- angular_compliance=angular_compliance,
1884
- armature=armature,
1885
- name=name,
1886
- collision_filter_parent=collision_filter_parent,
1887
- enabled=enabled,
1888
- )
1889
-
1890
- def add_joint_prismatic(
1891
- self,
1892
- parent: int,
1893
- child: int,
1894
- parent_xform: wp.transform | None = None,
1895
- child_xform: wp.transform | None = None,
1896
- axis: Vec3 = (1.0, 0.0, 0.0),
1897
- target: float | None = None,
1898
- target_ke: float = 0.0,
1899
- target_kd: float = 0.0,
1900
- mode: int = JOINT_MODE_FORCE,
1901
- limit_lower: float = -1e4,
1902
- limit_upper: float = 1e4,
1903
- limit_ke: float | None = None,
1904
- limit_kd: float | None = None,
1905
- linear_compliance: float = 0.0,
1906
- angular_compliance: float = 0.0,
1907
- armature: float = 1e-2,
1908
- name: str | None = None,
1909
- collision_filter_parent: bool = True,
1910
- enabled: bool = True,
1911
- ) -> int:
1912
- """Adds a prismatic (sliding) joint to the model. It has one degree of freedom.
1913
-
1914
- Args:
1915
- parent: The index of the parent body
1916
- child: The index of the child body
1917
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
1918
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
1919
- axis (3D vector or JointAxis): The axis of rotation in the parent body's local frame, can be a JointAxis object whose settings will be used instead of the other arguments
1920
- target: The target position or velocity of the joint (if None, the joint is considered to be in force control mode)
1921
- target_ke: The stiffness of the joint target
1922
- target_kd: The damping of the joint target
1923
- limit_lower: The lower limit of the joint
1924
- limit_upper: The upper limit of the joint
1925
- limit_ke: The stiffness of the joint limit (None to use the default value :attr:`default_joint_limit_ke`)
1926
- limit_kd: The damping of the joint limit (None to use the default value :attr:`default_joint_limit_ke`)
1927
- linear_compliance: The linear compliance of the joint
1928
- angular_compliance: The angular compliance of the joint
1929
- armature: Artificial inertia added around the joint axis
1930
- name: The name of the joint
1931
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
1932
- enabled: Whether the joint is enabled
1933
-
1934
- Returns:
1935
- The index of the added joint
1936
-
1937
- """
1938
- if parent_xform is None:
1939
- parent_xform = wp.transform()
1940
-
1941
- if child_xform is None:
1942
- child_xform = wp.transform()
1943
-
1944
- limit_ke = limit_ke if limit_ke is not None else self.default_joint_limit_ke
1945
- limit_kd = limit_kd if limit_kd is not None else self.default_joint_limit_kd
1946
-
1947
- action = 0.0
1948
- if target is None and mode == JOINT_MODE_TARGET_POSITION:
1949
- action = 0.5 * (limit_lower + limit_upper)
1950
- elif target is not None:
1951
- action = target
1952
- if mode == JOINT_MODE_FORCE:
1953
- mode = JOINT_MODE_TARGET_POSITION
1954
- ax = JointAxis(
1955
- axis=axis,
1956
- limit_lower=limit_lower,
1957
- limit_upper=limit_upper,
1958
- action=action,
1959
- target_ke=target_ke,
1960
- target_kd=target_kd,
1961
- mode=mode,
1962
- limit_ke=limit_ke,
1963
- limit_kd=limit_kd,
1964
- )
1965
- return self.add_joint(
1966
- JOINT_PRISMATIC,
1967
- parent,
1968
- child,
1969
- parent_xform=parent_xform,
1970
- child_xform=child_xform,
1971
- linear_axes=[ax],
1972
- linear_compliance=linear_compliance,
1973
- angular_compliance=angular_compliance,
1974
- armature=armature,
1975
- name=name,
1976
- collision_filter_parent=collision_filter_parent,
1977
- enabled=enabled,
1978
- )
1979
-
1980
- def add_joint_ball(
1981
- self,
1982
- parent: int,
1983
- child: int,
1984
- parent_xform: wp.transform | None = None,
1985
- child_xform: wp.transform | None = None,
1986
- linear_compliance: float = 0.0,
1987
- angular_compliance: float = 0.0,
1988
- armature: float = 1e-2,
1989
- name: str | None = None,
1990
- collision_filter_parent: bool = True,
1991
- enabled: bool = True,
1992
- ) -> int:
1993
- """Adds a ball (spherical) joint to the model. Its position is defined by a 4D quaternion (xyzw) and its velocity is a 3D vector.
1994
-
1995
- Args:
1996
- parent: The index of the parent body
1997
- child: The index of the child body
1998
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
1999
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2000
- linear_compliance: The linear compliance of the joint
2001
- angular_compliance: The angular compliance of the joint
2002
- armature (float): Artificial inertia added around the joint axis (only considered by FeatherstoneIntegrator)
2003
- name: The name of the joint
2004
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2005
- enabled: Whether the joint is enabled
2006
-
2007
- Returns:
2008
- The index of the added joint
2009
-
2010
- """
2011
- if parent_xform is None:
2012
- parent_xform = wp.transform()
2013
-
2014
- if child_xform is None:
2015
- child_xform = wp.transform()
2016
-
2017
- return self.add_joint(
2018
- JOINT_BALL,
2019
- parent,
2020
- child,
2021
- parent_xform=parent_xform,
2022
- child_xform=child_xform,
2023
- linear_compliance=linear_compliance,
2024
- angular_compliance=angular_compliance,
2025
- armature=armature,
2026
- name=name,
2027
- collision_filter_parent=collision_filter_parent,
2028
- enabled=enabled,
2029
- )
2030
-
2031
- def add_joint_fixed(
2032
- self,
2033
- parent: int,
2034
- child: int,
2035
- parent_xform: wp.transform | None = None,
2036
- child_xform: wp.transform | None = None,
2037
- linear_compliance: float = 0.0,
2038
- angular_compliance: float = 0.0,
2039
- armature: float = 1e-2,
2040
- name: str | None = None,
2041
- collision_filter_parent: bool = True,
2042
- enabled: bool = True,
2043
- ) -> int:
2044
- """Adds a fixed (static) joint to the model. It has no degrees of freedom.
2045
- See :meth:`collapse_fixed_joints` for a helper function that removes these fixed joints and merges the connecting bodies to simplify the model and improve stability.
2046
-
2047
- Args:
2048
- parent: The index of the parent body
2049
- child: The index of the child body
2050
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2051
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2052
- linear_compliance: The linear compliance of the joint
2053
- angular_compliance: The angular compliance of the joint
2054
- armature (float): Artificial inertia added around the joint axis (only considered by FeatherstoneIntegrator)
2055
- name: The name of the joint
2056
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2057
- enabled: Whether the joint is enabled
2058
-
2059
- Returns:
2060
- The index of the added joint
2061
-
2062
- """
2063
- if parent_xform is None:
2064
- parent_xform = wp.transform()
2065
-
2066
- if child_xform is None:
2067
- child_xform = wp.transform()
2068
-
2069
- return self.add_joint(
2070
- JOINT_FIXED,
2071
- parent,
2072
- child,
2073
- parent_xform=parent_xform,
2074
- child_xform=child_xform,
2075
- linear_compliance=linear_compliance,
2076
- angular_compliance=angular_compliance,
2077
- armature=armature,
2078
- name=name,
2079
- collision_filter_parent=collision_filter_parent,
2080
- enabled=enabled,
2081
- )
2082
-
2083
- def add_joint_free(
2084
- self,
2085
- child: int,
2086
- parent_xform: wp.transform | None = None,
2087
- child_xform: wp.transform | None = None,
2088
- armature: float = 0.0,
2089
- parent: int = -1,
2090
- name: str | None = None,
2091
- collision_filter_parent: bool = True,
2092
- enabled: bool = True,
2093
- ) -> int:
2094
- """Adds a free joint to the model.
2095
- It has 7 positional degrees of freedom (first 3 linear and then 4 angular dimensions for the orientation quaternion in `xyzw` notation) and 6 velocity degrees of freedom (first 3 angular and then 3 linear velocity dimensions).
2096
-
2097
- Args:
2098
- child: The index of the child body
2099
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2100
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2101
- armature (float): Artificial inertia added around the joint axis (only considered by FeatherstoneIntegrator)
2102
- parent: The index of the parent body (-1 by default to use the world frame, e.g. to make the child body and its children a floating-base mechanism)
2103
- name: The name of the joint
2104
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2105
- enabled: Whether the joint is enabled
2106
-
2107
- Returns:
2108
- The index of the added joint
2109
-
2110
- """
2111
- if parent_xform is None:
2112
- parent_xform = wp.transform()
2113
-
2114
- if child_xform is None:
2115
- child_xform = wp.transform()
2116
-
2117
- return self.add_joint(
2118
- JOINT_FREE,
2119
- parent,
2120
- child,
2121
- parent_xform=parent_xform,
2122
- child_xform=child_xform,
2123
- armature=armature,
2124
- name=name,
2125
- collision_filter_parent=collision_filter_parent,
2126
- enabled=enabled,
2127
- )
2128
-
2129
- def add_joint_distance(
2130
- self,
2131
- parent: int,
2132
- child: int,
2133
- parent_xform: wp.transform | None = None,
2134
- child_xform: wp.transform | None = None,
2135
- min_distance: float = -1.0,
2136
- max_distance: float = 1.0,
2137
- compliance: float = 0.0,
2138
- collision_filter_parent: bool = True,
2139
- enabled: bool = True,
2140
- ) -> int:
2141
- """Adds a distance joint to the model. The distance joint constraints the distance between the joint anchor points on the two bodies (see :ref:`FK-IK`) it connects to the interval [`min_distance`, `max_distance`].
2142
- It has 7 positional degrees of freedom (first 3 linear and then 4 angular dimensions for the orientation quaternion in `xyzw` notation) and 6 velocity degrees of freedom (first 3 angular and then 3 linear velocity dimensions).
2143
-
2144
- Args:
2145
- parent: The index of the parent body
2146
- child: The index of the child body
2147
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2148
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2149
- min_distance: The minimum distance between the bodies (no limit if negative)
2150
- max_distance: The maximum distance between the bodies (no limit if negative)
2151
- compliance: The compliance of the joint
2152
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2153
- enabled: Whether the joint is enabled
2154
-
2155
- Returns:
2156
- The index of the added joint
2157
-
2158
- .. note:: Distance joints are currently only supported in the :class:`XPBDIntegrator` at the moment.
2159
-
2160
- """
2161
- if parent_xform is None:
2162
- parent_xform = wp.transform()
2163
-
2164
- if child_xform is None:
2165
- child_xform = wp.transform()
2166
-
2167
- ax = JointAxis(
2168
- axis=(1.0, 0.0, 0.0),
2169
- limit_lower=min_distance,
2170
- limit_upper=max_distance,
2171
- )
2172
- return self.add_joint(
2173
- JOINT_DISTANCE,
2174
- parent,
2175
- child,
2176
- parent_xform=parent_xform,
2177
- child_xform=child_xform,
2178
- linear_axes=[ax],
2179
- linear_compliance=compliance,
2180
- collision_filter_parent=collision_filter_parent,
2181
- enabled=enabled,
2182
- )
2183
-
2184
- def add_joint_universal(
2185
- self,
2186
- parent: int,
2187
- child: int,
2188
- axis_0: JointAxis,
2189
- axis_1: JointAxis,
2190
- parent_xform: wp.transform | None = None,
2191
- child_xform: wp.transform | None = None,
2192
- linear_compliance: float = 0.0,
2193
- angular_compliance: float = 0.0,
2194
- armature: float = 1e-2,
2195
- name: str | None = None,
2196
- collision_filter_parent: bool = True,
2197
- enabled: bool = True,
2198
- ) -> int:
2199
- """Adds a universal joint to the model. U-joints have two degrees of freedom, one for each axis.
2200
-
2201
- Args:
2202
- parent: The index of the parent body
2203
- child: The index of the child body
2204
- axis_0 (3D vector or JointAxis): The first axis of the joint, can be a JointAxis object whose settings will be used instead of the other arguments
2205
- axis_1 (3D vector or JointAxis): The second axis of the joint, can be a JointAxis object whose settings will be used instead of the other arguments
2206
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2207
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2208
- linear_compliance: The linear compliance of the joint
2209
- angular_compliance: The angular compliance of the joint
2210
- armature: Artificial inertia added around the joint axes
2211
- name: The name of the joint
2212
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2213
- enabled: Whether the joint is enabled
2214
-
2215
- Returns:
2216
- The index of the added joint
2217
-
2218
- """
2219
- if parent_xform is None:
2220
- parent_xform = wp.transform()
2221
-
2222
- if child_xform is None:
2223
- child_xform = wp.transform()
2224
-
2225
- return self.add_joint(
2226
- JOINT_UNIVERSAL,
2227
- parent,
2228
- child,
2229
- angular_axes=[JointAxis(axis_0), JointAxis(axis_1)],
2230
- parent_xform=parent_xform,
2231
- child_xform=child_xform,
2232
- linear_compliance=linear_compliance,
2233
- angular_compliance=angular_compliance,
2234
- armature=armature,
2235
- name=name,
2236
- collision_filter_parent=collision_filter_parent,
2237
- enabled=enabled,
2238
- )
2239
-
2240
- def add_joint_compound(
2241
- self,
2242
- parent: int,
2243
- child: int,
2244
- axis_0: JointAxis,
2245
- axis_1: JointAxis,
2246
- axis_2: JointAxis,
2247
- parent_xform: wp.transform | None = None,
2248
- child_xform: wp.transform | None = None,
2249
- linear_compliance: float = 0.0,
2250
- angular_compliance: float = 0.0,
2251
- armature: float = 1e-2,
2252
- name: str | None = None,
2253
- collision_filter_parent: bool = True,
2254
- enabled: bool = True,
2255
- ) -> int:
2256
- """Adds a compound joint to the model, which has 3 degrees of freedom, one for each axis.
2257
- Similar to the ball joint (see :meth:`add_ball_joint`), the compound joint allows bodies to move in a 3D rotation relative to each other,
2258
- except that the rotation is defined by 3 axes instead of a quaternion.
2259
- Depending on the choice of axes, the orientation can be specified through Euler angles, e.g. `z-x-z` or `x-y-x`, or through a Tait-Bryan angle sequence, e.g. `z-y-x` or `x-y-z`.
2260
-
2261
- Args:
2262
- parent: The index of the parent body
2263
- child: The index of the child body
2264
- axis_0 (3D vector or JointAxis): The first axis of the joint, can be a JointAxis object whose settings will be used instead of the other arguments
2265
- axis_1 (3D vector or JointAxis): The second axis of the joint, can be a JointAxis object whose settings will be used instead of the other arguments
2266
- axis_2 (3D vector or JointAxis): The third axis of the joint, can be a JointAxis object whose settings will be used instead of the other arguments
2267
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2268
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2269
- linear_compliance: The linear compliance of the joint
2270
- angular_compliance: The angular compliance of the joint
2271
- armature: Artificial inertia added around the joint axes
2272
- name: The name of the joint
2273
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2274
- enabled: Whether the joint is enabled
2275
-
2276
- Returns:
2277
- The index of the added joint
2278
-
2279
- """
2280
- if parent_xform is None:
2281
- parent_xform = wp.transform()
2282
-
2283
- if child_xform is None:
2284
- child_xform = wp.transform()
2285
-
2286
- return self.add_joint(
2287
- JOINT_COMPOUND,
2288
- parent,
2289
- child,
2290
- angular_axes=[JointAxis(axis_0), JointAxis(axis_1), JointAxis(axis_2)],
2291
- parent_xform=parent_xform,
2292
- child_xform=child_xform,
2293
- linear_compliance=linear_compliance,
2294
- angular_compliance=angular_compliance,
2295
- armature=armature,
2296
- name=name,
2297
- collision_filter_parent=collision_filter_parent,
2298
- enabled=enabled,
2299
- )
2300
-
2301
- def add_joint_d6(
2302
- self,
2303
- parent: int,
2304
- child: int,
2305
- linear_axes: list[JointAxis] | None = None,
2306
- angular_axes: list[JointAxis] | None = None,
2307
- name: str | None = None,
2308
- parent_xform: wp.transform | None = None,
2309
- child_xform: wp.transform | None = None,
2310
- linear_compliance: float = 0.0,
2311
- angular_compliance: float = 0.0,
2312
- armature: float = 1e-2,
2313
- collision_filter_parent: bool = True,
2314
- enabled: bool = True,
2315
- ):
2316
- """Adds a generic joint with custom linear and angular axes. The number of axes determines the number of degrees of freedom of the joint.
2317
-
2318
- Args:
2319
- parent: The index of the parent body
2320
- child: The index of the child body
2321
- linear_axes: A list of linear axes
2322
- angular_axes: A list of angular axes
2323
- name: The name of the joint
2324
- parent_xform (:ref:`transform <transform>`): The transform of the joint in the parent body's local frame
2325
- child_xform (:ref:`transform <transform>`): The transform of the joint in the child body's local frame
2326
- linear_compliance: The linear compliance of the joint
2327
- angular_compliance: The angular compliance of the joint
2328
- armature: Artificial inertia added around the joint axes
2329
- collision_filter_parent: Whether to filter collisions between shapes of the parent and child bodies
2330
- enabled: Whether the joint is enabled
2331
-
2332
- Returns:
2333
- The index of the added joint
2334
-
2335
- """
2336
- if linear_axes is None:
2337
- linear_axes = []
2338
-
2339
- if angular_axes is None:
2340
- angular_axes = []
2341
-
2342
- if parent_xform is None:
2343
- parent_xform = wp.transform()
2344
-
2345
- if child_xform is None:
2346
- child_xform = wp.transform()
2347
-
2348
- return self.add_joint(
2349
- JOINT_D6,
2350
- parent,
2351
- child,
2352
- parent_xform=parent_xform,
2353
- child_xform=child_xform,
2354
- linear_axes=[JointAxis(a) for a in linear_axes],
2355
- angular_axes=[JointAxis(a) for a in angular_axes],
2356
- linear_compliance=linear_compliance,
2357
- angular_compliance=angular_compliance,
2358
- armature=armature,
2359
- name=name,
2360
- collision_filter_parent=collision_filter_parent,
2361
- enabled=enabled,
2362
- )
2363
-
2364
- def plot_articulation(
2365
- self,
2366
- show_body_names=True,
2367
- show_joint_names=True,
2368
- show_joint_types=True,
2369
- plot_shapes=True,
2370
- show_shape_types=True,
2371
- show_legend=True,
2372
- ):
2373
- """
2374
- Visualizes the model's articulation graph using matplotlib and networkx.
2375
- Uses the spring layout algorithm from networkx to arrange the nodes.
2376
- Bodies are shown as orange squares, shapes are shown as blue circles.
2377
-
2378
- Args:
2379
- show_body_names (bool): Whether to show the body names or indices
2380
- show_joint_names (bool): Whether to show the joint names or indices
2381
- show_joint_types (bool): Whether to show the joint types
2382
- plot_shapes (bool): Whether to render the shapes connected to the rigid bodies
2383
- show_shape_types (bool): Whether to show the shape geometry types
2384
- show_legend (bool): Whether to show a legend
2385
- """
2386
- import matplotlib.pyplot as plt
2387
- import networkx as nx
2388
-
2389
- def joint_type_str(type):
2390
- if type == JOINT_FREE:
2391
- return "free"
2392
- elif type == JOINT_BALL:
2393
- return "ball"
2394
- elif type == JOINT_PRISMATIC:
2395
- return "prismatic"
2396
- elif type == JOINT_REVOLUTE:
2397
- return "revolute"
2398
- elif type == JOINT_D6:
2399
- return "D6"
2400
- elif type == JOINT_UNIVERSAL:
2401
- return "universal"
2402
- elif type == JOINT_COMPOUND:
2403
- return "compound"
2404
- elif type == JOINT_FIXED:
2405
- return "fixed"
2406
- elif type == JOINT_DISTANCE:
2407
- return "distance"
2408
- return "unknown"
2409
-
2410
- def shape_type_str(type):
2411
- if type == GEO_SPHERE:
2412
- return "sphere"
2413
- if type == GEO_BOX:
2414
- return "box"
2415
- if type == GEO_CAPSULE:
2416
- return "capsule"
2417
- if type == GEO_CYLINDER:
2418
- return "cylinder"
2419
- if type == GEO_CONE:
2420
- return "cone"
2421
- if type == GEO_MESH:
2422
- return "mesh"
2423
- if type == GEO_SDF:
2424
- return "sdf"
2425
- if type == GEO_PLANE:
2426
- return "plane"
2427
- if type == GEO_NONE:
2428
- return "none"
2429
- return "unknown"
2430
-
2431
- if show_body_names:
2432
- vertices = ["world", *self.body_name]
2433
- else:
2434
- vertices = ["-1"] + [str(i) for i in range(self.body_count)]
2435
- if plot_shapes:
2436
- for i in range(self.shape_count):
2437
- shape_label = f"shape_{i}"
2438
- if show_shape_types:
2439
- shape_label += f"\n({shape_type_str(self.shape_geo_type[i])})"
2440
- vertices.append(shape_label)
2441
- edges = []
2442
- edge_labels = []
2443
- for i in range(self.joint_count):
2444
- edge = (self.joint_child[i] + 1, self.joint_parent[i] + 1)
2445
- edges.append(edge)
2446
- if show_joint_names:
2447
- joint_label = self.joint_name[i]
2448
- else:
2449
- joint_label = str(i)
2450
- if show_joint_types:
2451
- joint_label += f"\n({joint_type_str(self.joint_type[i])})"
2452
- edge_labels.append(joint_label)
2453
-
2454
- if plot_shapes:
2455
- for i in range(self.shape_count):
2456
- edges.append((len(self.body_name) + i + 1, self.shape_body[i] + 1))
2457
-
2458
- # plot graph
2459
- G = nx.Graph()
2460
- for i in range(len(vertices)):
2461
- G.add_node(i, label=vertices[i])
2462
- for i in range(len(edges)):
2463
- label = edge_labels[i] if i < len(edge_labels) else ""
2464
- G.add_edge(edges[i][0], edges[i][1], label=label)
2465
- pos = nx.spring_layout(G)
2466
- nx.draw_networkx_edges(G, pos, node_size=0, edgelist=edges[: self.joint_count])
2467
- # render body vertices
2468
- draw_args = {"node_size": 100}
2469
- bodies = nx.subgraph(G, list(range(self.body_count + 1)))
2470
- nx.draw_networkx_nodes(bodies, pos, node_color="orange", node_shape="s", **draw_args)
2471
- if plot_shapes:
2472
- # render shape vertices
2473
- shapes = nx.subgraph(G, list(range(self.body_count + 1, len(vertices))))
2474
- nx.draw_networkx_nodes(shapes, pos, node_color="skyblue", **draw_args)
2475
- nx.draw_networkx_edges(
2476
- G, pos, node_size=0, edgelist=edges[self.joint_count :], edge_color="gray", style="dashed"
2477
- )
2478
- edge_labels = nx.get_edge_attributes(G, "label")
2479
- nx.draw_networkx_edge_labels(
2480
- G, pos, edge_labels=edge_labels, font_size=6, bbox={"alpha": 0.6, "color": "w", "lw": 0}
2481
- )
2482
- # add node labels
2483
- nx.draw_networkx_labels(G, pos, dict(enumerate(vertices)), font_size=6)
2484
- if show_legend:
2485
- plt.plot([], [], "s", color="orange", label="body")
2486
- plt.plot([], [], "k-", label="joint")
2487
- if plot_shapes:
2488
- plt.plot([], [], "o", color="skyblue", label="shape")
2489
- plt.plot([], [], "k--", label="shape-body connection")
2490
- plt.legend(loc="upper left", fontsize=6)
2491
- plt.show()
2492
-
2493
- def collapse_fixed_joints(self, verbose=wp.config.verbose):
2494
- """Removes fixed joints from the model and merges the bodies they connect. This is useful for simplifying the model for faster and more stable simulation."""
2495
-
2496
- body_data = {}
2497
- body_children = {-1: []}
2498
- visited = {}
2499
- for i in range(self.body_count):
2500
- name = self.body_name[i]
2501
- body_data[i] = {
2502
- "shapes": self.body_shapes[i],
2503
- "q": self.body_q[i],
2504
- "qd": self.body_qd[i],
2505
- "mass": self.body_mass[i],
2506
- "inertia": self.body_inertia[i],
2507
- "inv_mass": self.body_inv_mass[i],
2508
- "inv_inertia": self.body_inv_inertia[i],
2509
- "com": self.body_com[i],
2510
- "name": name,
2511
- "original_id": i,
2512
- }
2513
- visited[i] = False
2514
- body_children[i] = []
2515
-
2516
- joint_data = {}
2517
- for i in range(self.joint_count):
2518
- name = self.joint_name[i]
2519
- parent = self.joint_parent[i]
2520
- child = self.joint_child[i]
2521
- body_children[parent].append(child)
2522
-
2523
- q_start = self.joint_q_start[i]
2524
- qd_start = self.joint_qd_start[i]
2525
- if i < self.joint_count - 1:
2526
- q_dim = self.joint_q_start[i + 1] - q_start
2527
- qd_dim = self.joint_qd_start[i + 1] - qd_start
2528
- else:
2529
- q_dim = len(self.joint_q) - q_start
2530
- qd_dim = len(self.joint_qd) - qd_start
2531
-
2532
- data = {
2533
- "type": self.joint_type[i],
2534
- "q": self.joint_q[q_start : q_start + q_dim],
2535
- "qd": self.joint_qd[qd_start : qd_start + qd_dim],
2536
- "armature": self.joint_armature[qd_start : qd_start + qd_dim],
2537
- "q_start": q_start,
2538
- "qd_start": qd_start,
2539
- "linear_compliance": self.joint_linear_compliance[i],
2540
- "angular_compliance": self.joint_angular_compliance[i],
2541
- "name": name,
2542
- "parent_xform": wp.transform_expand(self.joint_X_p[i]),
2543
- "child_xform": wp.transform_expand(self.joint_X_c[i]),
2544
- "enabled": self.joint_enabled[i],
2545
- "axes": [],
2546
- "axis_dim": self.joint_axis_dim[i],
2547
- "parent": parent,
2548
- "child": child,
2549
- "original_id": i,
2550
- }
2551
- num_lin_axes, num_ang_axes = self.joint_axis_dim[i]
2552
- start_ax = self.joint_axis_start[i]
2553
- for j in range(start_ax, start_ax + num_lin_axes + num_ang_axes):
2554
- data["axes"].append(
2555
- {
2556
- "axis": self.joint_axis[j],
2557
- "axis_mode": self.joint_axis_mode[j],
2558
- "target_ke": self.joint_target_ke[j],
2559
- "target_kd": self.joint_target_kd[j],
2560
- "limit_ke": self.joint_limit_ke[j],
2561
- "limit_kd": self.joint_limit_kd[j],
2562
- "limit_lower": self.joint_limit_lower[j],
2563
- "limit_upper": self.joint_limit_upper[j],
2564
- "act": self.joint_act[j],
2565
- }
2566
- )
2567
-
2568
- joint_data[(parent, child)] = data
2569
-
2570
- # sort body children so we traverse the tree in the same order as the bodies are listed
2571
- for children in body_children.values():
2572
- children.sort(key=lambda x: body_data[x]["original_id"])
2573
-
2574
- retained_joints = []
2575
- retained_bodies = []
2576
- body_remap = {-1: -1}
2577
-
2578
- # depth first search over the joint graph
2579
- def dfs(parent_body: int, child_body: int, incoming_xform: wp.transform, last_dynamic_body: int):
2580
- nonlocal visited
2581
- nonlocal retained_joints
2582
- nonlocal retained_bodies
2583
- nonlocal body_data
2584
- nonlocal body_remap
2585
-
2586
- joint = joint_data[(parent_body, child_body)]
2587
- if joint["type"] == JOINT_FIXED:
2588
- joint_xform = joint["parent_xform"] * wp.transform_inverse(joint["child_xform"])
2589
- incoming_xform = incoming_xform * joint_xform
2590
- parent_name = self.body_name[parent_body] if parent_body > -1 else "world"
2591
- child_name = self.body_name[child_body]
2592
- last_dynamic_body_name = self.body_name[last_dynamic_body] if last_dynamic_body > -1 else "world"
2593
- if verbose:
2594
- print(
2595
- f"Remove fixed joint {joint['name']} between {parent_name} and {child_name}, "
2596
- f"merging {child_name} into {last_dynamic_body_name}"
2597
- )
2598
- child_id = body_data[child_body]["original_id"]
2599
- for shape in self.body_shapes[child_id]:
2600
- self.shape_transform[shape] = incoming_xform * self.shape_transform[shape]
2601
- if verbose:
2602
- print(
2603
- f" Shape {shape} moved to body {last_dynamic_body_name} with transform {self.shape_transform[shape]}"
2604
- )
2605
- if last_dynamic_body > -1:
2606
- self.shape_body[shape] = body_data[last_dynamic_body]["id"]
2607
- body_data[last_dynamic_body]["shapes"].append(shape)
2608
- else:
2609
- self.shape_body[shape] = -1
2610
-
2611
- if last_dynamic_body > -1:
2612
- source_m = body_data[last_dynamic_body]["mass"]
2613
- source_com = body_data[last_dynamic_body]["com"]
2614
- # add inertia to last_dynamic_body
2615
- m = body_data[child_body]["mass"]
2616
- com = wp.transform_point(incoming_xform, body_data[child_body]["com"])
2617
- inertia = body_data[child_body]["inertia"]
2618
- body_data[last_dynamic_body]["inertia"] += wp.sim.transform_inertia(
2619
- m, inertia, incoming_xform.p, incoming_xform.q
2620
- )
2621
- body_data[last_dynamic_body]["mass"] += m
2622
- body_data[last_dynamic_body]["com"] = (m * com + source_m * source_com) / (m + source_m)
2623
- # indicate to recompute inverse mass, inertia for this body
2624
- body_data[last_dynamic_body]["inv_mass"] = None
2625
- else:
2626
- joint["parent_xform"] = incoming_xform * joint["parent_xform"]
2627
- joint["parent"] = last_dynamic_body
2628
- last_dynamic_body = child_body
2629
- incoming_xform = wp.transform()
2630
- retained_joints.append(joint)
2631
- new_id = len(retained_bodies)
2632
- body_data[child_body]["id"] = new_id
2633
- retained_bodies.append(child_body)
2634
- for shape in body_data[child_body]["shapes"]:
2635
- self.shape_body[shape] = new_id
2636
-
2637
- visited[parent_body] = True
2638
- if visited[child_body] or child_body not in body_children:
2639
- return
2640
- for child in body_children[child_body]:
2641
- if not visited[child]:
2642
- dfs(child_body, child, incoming_xform, last_dynamic_body)
2643
-
2644
- for body in body_children[-1]:
2645
- if not visited[body]:
2646
- dfs(-1, body, wp.transform(), -1)
2647
-
2648
- # repopulate the model
2649
- self.body_name.clear()
2650
- self.body_q.clear()
2651
- self.body_qd.clear()
2652
- self.body_mass.clear()
2653
- self.body_inertia.clear()
2654
- self.body_com.clear()
2655
- self.body_inv_mass.clear()
2656
- self.body_inv_inertia.clear()
2657
- self.body_shapes.clear()
2658
- for i in retained_bodies:
2659
- body = body_data[i]
2660
- new_id = len(self.body_name)
2661
- body_remap[body["original_id"]] = new_id
2662
- self.body_name.append(body["name"])
2663
- self.body_q.append(list(body["q"]))
2664
- self.body_qd.append(list(body["qd"]))
2665
- m = body["mass"]
2666
- inertia = body["inertia"]
2667
- self.body_mass.append(m)
2668
- self.body_inertia.append(inertia)
2669
- self.body_com.append(body["com"])
2670
- if body["inv_mass"] is None:
2671
- # recompute inverse mass and inertia
2672
- if m > 0.0:
2673
- self.body_inv_mass.append(1.0 / m)
2674
- self.body_inv_inertia.append(wp.inverse(inertia))
2675
- else:
2676
- self.body_inv_mass.append(0.0)
2677
- self.body_inv_inertia.append(wp.mat33(0.0))
2678
- else:
2679
- self.body_inv_mass.append(body["inv_mass"])
2680
- self.body_inv_inertia.append(body["inv_inertia"])
2681
- self.body_shapes[new_id] = body["shapes"]
2682
- body_remap[body["original_id"]] = new_id
2683
-
2684
- # sort joints so they appear in the same order as before
2685
- retained_joints.sort(key=lambda x: x["original_id"])
2686
-
2687
- joint_remap = {}
2688
- for i, joint in enumerate(retained_joints):
2689
- joint_remap[joint["original_id"]] = i
2690
- # update articulation_start
2691
- for i, old_i in enumerate(self.articulation_start):
2692
- start_i = old_i
2693
- while start_i not in joint_remap:
2694
- start_i += 1
2695
- if start_i >= self.joint_count:
2696
- break
2697
- self.articulation_start[i] = joint_remap.get(start_i, start_i)
2698
- # remove empty articulation starts, i.e. where the start and end are the same
2699
- self.articulation_start = list(set(self.articulation_start))
2700
-
2701
- self.joint_name.clear()
2702
- self.joint_type.clear()
2703
- self.joint_parent.clear()
2704
- self.joint_child.clear()
2705
- self.joint_q.clear()
2706
- self.joint_qd.clear()
2707
- self.joint_q_start.clear()
2708
- self.joint_qd_start.clear()
2709
- self.joint_enabled.clear()
2710
- self.joint_linear_compliance.clear()
2711
- self.joint_angular_compliance.clear()
2712
- self.joint_armature.clear()
2713
- self.joint_X_p.clear()
2714
- self.joint_X_c.clear()
2715
- self.joint_axis.clear()
2716
- self.joint_axis_mode.clear()
2717
- self.joint_target_ke.clear()
2718
- self.joint_target_kd.clear()
2719
- self.joint_limit_lower.clear()
2720
- self.joint_limit_upper.clear()
2721
- self.joint_limit_ke.clear()
2722
- self.joint_limit_kd.clear()
2723
- self.joint_axis_dim.clear()
2724
- self.joint_axis_start.clear()
2725
- self.joint_act.clear()
2726
- for joint in retained_joints:
2727
- self.joint_name.append(joint["name"])
2728
- self.joint_type.append(joint["type"])
2729
- self.joint_parent.append(body_remap[joint["parent"]])
2730
- self.joint_child.append(body_remap[joint["child"]])
2731
- self.joint_q_start.append(len(self.joint_q))
2732
- self.joint_qd_start.append(len(self.joint_qd))
2733
- self.joint_q.extend(joint["q"])
2734
- self.joint_qd.extend(joint["qd"])
2735
- self.joint_armature.extend(joint["armature"])
2736
- self.joint_enabled.append(joint["enabled"])
2737
- self.joint_linear_compliance.append(joint["linear_compliance"])
2738
- self.joint_angular_compliance.append(joint["angular_compliance"])
2739
- self.joint_X_p.append(list(joint["parent_xform"]))
2740
- self.joint_X_c.append(list(joint["child_xform"]))
2741
- self.joint_axis_dim.append(joint["axis_dim"])
2742
- self.joint_axis_start.append(len(self.joint_axis))
2743
- for axis in joint["axes"]:
2744
- self.joint_axis.append(axis["axis"])
2745
- self.joint_axis_mode.append(axis["axis_mode"])
2746
- self.joint_target_ke.append(axis["target_ke"])
2747
- self.joint_target_kd.append(axis["target_kd"])
2748
- self.joint_limit_lower.append(axis["limit_lower"])
2749
- self.joint_limit_upper.append(axis["limit_upper"])
2750
- self.joint_limit_ke.append(axis["limit_ke"])
2751
- self.joint_limit_kd.append(axis["limit_kd"])
2752
- self.joint_act.append(axis["act"])
2753
-
2754
- # muscles
2755
- def add_muscle(
2756
- self, bodies: list[int], positions: list[Vec3], f0: float, lm: float, lt: float, lmax: float, pen: float
2757
- ) -> float:
2758
- """Adds a muscle-tendon activation unit.
2759
-
2760
- Args:
2761
- bodies: A list of body indices for each waypoint
2762
- positions: A list of positions of each waypoint in the body's local frame
2763
- f0: Force scaling
2764
- lm: Muscle length
2765
- lt: Tendon length
2766
- lmax: Maximally efficient muscle length
2767
-
2768
- Returns:
2769
- The index of the muscle in the model
2770
-
2771
- .. note:: The simulation support for muscles is in progress and not yet fully functional.
2772
-
2773
- """
2774
-
2775
- n = len(bodies)
2776
-
2777
- self.muscle_start.append(len(self.muscle_bodies))
2778
- self.muscle_params.append((f0, lm, lt, lmax, pen))
2779
- self.muscle_activations.append(0.0)
2780
-
2781
- for i in range(n):
2782
- self.muscle_bodies.append(bodies[i])
2783
- self.muscle_points.append(positions[i])
2784
-
2785
- # return the index of the muscle
2786
- return len(self.muscle_start) - 1
2787
-
2788
- # shapes
2789
- def add_shape_plane(
2790
- self,
2791
- plane: Vec4 | tuple[float, float, float, float] = (0.0, 1.0, 0.0, 0.0),
2792
- pos: Vec3 | None = None,
2793
- rot: Quat | None = None,
2794
- width: float = 10.0,
2795
- length: float = 10.0,
2796
- body: int = -1,
2797
- ke: float | None = None,
2798
- kd: float | None = None,
2799
- kf: float | None = None,
2800
- ka: float | None = None,
2801
- mu: float | None = None,
2802
- restitution: float | None = None,
2803
- thickness: float | None = None,
2804
- has_ground_collision: bool = False,
2805
- has_shape_collision: bool = True,
2806
- is_visible: bool = True,
2807
- collision_group: int = -1,
2808
- ) -> int:
2809
- """Add a plane collision shape.
2810
-
2811
- If ``pos`` and ``rot`` are defined, the plane is assumed to have its normal as (0, 1, 0).
2812
- Otherwise, the plane equation defined through the ``plane`` argument is used.
2813
-
2814
- Args:
2815
- plane: The plane equation in form a*x + b*y + c*z + d = 0
2816
- pos: The position of the plane in world coordinates
2817
- rot: The rotation of the plane in world coordinates
2818
- width: The extent along x of the plane (infinite if 0)
2819
- length: The extent along z of the plane (infinite if 0)
2820
- body: The body index to attach the shape to (-1 by default to keep the plane static)
2821
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
2822
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
2823
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
2824
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
2825
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
2826
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
2827
- thickness: The thickness of the plane (0 by default) for collision handling (None to use the default value :attr:`default_shape_thickness`)
2828
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
2829
- has_shape_collision: If True, the shape will collide with other shapes
2830
- is_visible: Whether the plane is visible
2831
- collision_group: The collision group of the shape
2832
-
2833
- Returns:
2834
- The index of the added shape
2835
-
2836
- """
2837
- if pos is None or rot is None:
2838
- # compute position and rotation from plane equation
2839
- normal = np.array(plane[:3])
2840
- normal /= np.linalg.norm(normal)
2841
- pos = plane[3] * normal
2842
- if np.allclose(normal, (0.0, 1.0, 0.0)):
2843
- # no rotation necessary
2844
- rot = (0.0, 0.0, 0.0, 1.0)
2845
- else:
2846
- c = np.cross(normal, (0.0, 1.0, 0.0))
2847
- angle = np.arcsin(np.linalg.norm(c))
2848
- axis = np.abs(c) / np.linalg.norm(c)
2849
- rot = wp.quat_from_axis_angle(wp.vec3(*axis), wp.float32(angle))
2850
- scale = wp.vec3(width, length, 0.0)
2851
-
2852
- return self._add_shape(
2853
- body,
2854
- pos,
2855
- rot,
2856
- GEO_PLANE,
2857
- scale,
2858
- None,
2859
- 0.0,
2860
- ke,
2861
- kd,
2862
- kf,
2863
- ka,
2864
- mu,
2865
- restitution,
2866
- thickness,
2867
- has_ground_collision=has_ground_collision,
2868
- has_shape_collision=has_shape_collision,
2869
- is_visible=is_visible,
2870
- collision_group=collision_group,
2871
- )
2872
-
2873
- def add_shape_sphere(
2874
- self,
2875
- body,
2876
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
2877
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
2878
- radius: float = 1.0,
2879
- density: float | None = None,
2880
- ke: float | None = None,
2881
- kd: float | None = None,
2882
- kf: float | None = None,
2883
- ka: float | None = None,
2884
- mu: float | None = None,
2885
- restitution: float | None = None,
2886
- is_solid: bool = True,
2887
- thickness: float | None = None,
2888
- has_ground_collision: bool = True,
2889
- has_shape_collision: bool = True,
2890
- collision_group: int = -1,
2891
- is_visible: bool = True,
2892
- ) -> int:
2893
- """Add a sphere collision shape to a body.
2894
-
2895
- Args:
2896
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
2897
- pos: The location of the shape with respect to the parent frame
2898
- rot: The rotation of the shape with respect to the parent frame
2899
- radius: The radius of the sphere
2900
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
2901
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
2902
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
2903
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
2904
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
2905
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
2906
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
2907
- is_solid: Whether the sphere is solid or hollow
2908
- thickness: Thickness to use for computing inertia of a hollow sphere, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
2909
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
2910
- has_shape_collision: If True, the shape will collide with other shapes
2911
- collision_group: The collision group of the shape
2912
- is_visible: Whether the sphere is visible
2913
-
2914
- Returns:
2915
- The index of the added shape
2916
-
2917
- """
2918
-
2919
- thickness = self.default_shape_thickness if thickness is None else thickness
2920
- return self._add_shape(
2921
- body,
2922
- wp.vec3(pos),
2923
- wp.quat(rot),
2924
- GEO_SPHERE,
2925
- wp.vec3(radius, 0.0, 0.0),
2926
- None,
2927
- density,
2928
- ke,
2929
- kd,
2930
- kf,
2931
- ka,
2932
- mu,
2933
- restitution,
2934
- thickness + radius,
2935
- is_solid,
2936
- has_ground_collision=has_ground_collision,
2937
- has_shape_collision=has_shape_collision,
2938
- collision_group=collision_group,
2939
- is_visible=is_visible,
2940
- )
2941
-
2942
- def add_shape_box(
2943
- self,
2944
- body: int,
2945
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
2946
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
2947
- hx: float = 0.5,
2948
- hy: float = 0.5,
2949
- hz: float = 0.5,
2950
- density: float | None = None,
2951
- ke: float | None = None,
2952
- kd: float | None = None,
2953
- kf: float | None = None,
2954
- ka: float | None = None,
2955
- mu: float | None = None,
2956
- restitution: float | None = None,
2957
- is_solid: bool = True,
2958
- thickness: float | None = None,
2959
- has_ground_collision: bool = True,
2960
- has_shape_collision: bool = True,
2961
- collision_group: int = -1,
2962
- is_visible: bool = True,
2963
- ) -> int:
2964
- """Add a box collision shape to a body.
2965
-
2966
- Args:
2967
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
2968
- pos: The location of the shape with respect to the parent frame
2969
- rot: The rotation of the shape with respect to the parent frame
2970
- hx: The half-extent along the x-axis
2971
- hy: The half-extent along the y-axis
2972
- hz: The half-extent along the z-axis
2973
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
2974
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
2975
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
2976
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
2977
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
2978
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
2979
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
2980
- is_solid: Whether the box is solid or hollow
2981
- thickness: Thickness to use for computing inertia of a hollow box, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
2982
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
2983
- has_shape_collision: If True, the shape will collide with other shapes
2984
- collision_group: The collision group of the shape
2985
- is_visible: Whether the box is visible
2986
-
2987
- Returns:
2988
- The index of the added shape
2989
- """
2990
-
2991
- return self._add_shape(
2992
- body,
2993
- wp.vec3(pos),
2994
- wp.quat(rot),
2995
- GEO_BOX,
2996
- wp.vec3(hx, hy, hz),
2997
- None,
2998
- density,
2999
- ke,
3000
- kd,
3001
- kf,
3002
- ka,
3003
- mu,
3004
- restitution,
3005
- thickness,
3006
- is_solid,
3007
- has_ground_collision=has_ground_collision,
3008
- has_shape_collision=has_shape_collision,
3009
- collision_group=collision_group,
3010
- is_visible=is_visible,
3011
- )
3012
-
3013
- def add_shape_capsule(
3014
- self,
3015
- body: int,
3016
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
3017
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
3018
- radius: float = 1.0,
3019
- half_height: float = 0.5,
3020
- up_axis: int = 1,
3021
- density: float | None = None,
3022
- ke: float | None = None,
3023
- kd: float | None = None,
3024
- kf: float | None = None,
3025
- ka: float | None = None,
3026
- mu: float | None = None,
3027
- restitution: float | None = None,
3028
- is_solid: bool = True,
3029
- thickness: float | None = None,
3030
- has_ground_collision: bool = True,
3031
- has_shape_collision: bool = True,
3032
- collision_group: int = -1,
3033
- is_visible: bool = True,
3034
- ) -> int:
3035
- """Add a capsule collision shape to a body.
3036
-
3037
- Args:
3038
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
3039
- pos: The location of the shape with respect to the parent frame
3040
- rot: The rotation of the shape with respect to the parent frame
3041
- radius: The radius of the capsule
3042
- half_height: The half length of the center cylinder along the up axis
3043
- up_axis: The axis along which the capsule is aligned (0=x, 1=y, 2=z)
3044
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
3045
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
3046
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
3047
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
3048
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
3049
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
3050
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
3051
- is_solid: Whether the capsule is solid or hollow
3052
- thickness: Thickness to use for computing inertia of a hollow capsule, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
3053
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
3054
- has_shape_collision: If True, the shape will collide with other shapes
3055
- collision_group: The collision group of the shape
3056
- is_visible: Whether the capsule is visible
3057
-
3058
- Returns:
3059
- The index of the added shape
3060
-
3061
- """
3062
-
3063
- q = wp.quat(rot)
3064
- sqh = math.sqrt(0.5)
3065
- if up_axis == 0:
3066
- q = wp.mul(q, wp.quat(0.0, 0.0, -sqh, sqh))
3067
- elif up_axis == 2:
3068
- q = wp.mul(q, wp.quat(sqh, 0.0, 0.0, sqh))
3069
-
3070
- thickness = self.default_shape_thickness if thickness is None else thickness
3071
- return self._add_shape(
3072
- body,
3073
- wp.vec3(pos),
3074
- wp.quat(q),
3075
- GEO_CAPSULE,
3076
- wp.vec3(radius, half_height, 0.0),
3077
- None,
3078
- density,
3079
- ke,
3080
- kd,
3081
- kf,
3082
- ka,
3083
- mu,
3084
- restitution,
3085
- thickness + radius,
3086
- is_solid,
3087
- has_ground_collision=has_ground_collision,
3088
- has_shape_collision=has_shape_collision,
3089
- collision_group=collision_group,
3090
- is_visible=is_visible,
3091
- )
3092
-
3093
- def add_shape_cylinder(
3094
- self,
3095
- body: int,
3096
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
3097
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
3098
- radius: float = 1.0,
3099
- half_height: float = 0.5,
3100
- up_axis: int = 1,
3101
- density: float | None = None,
3102
- ke: float | None = None,
3103
- kd: float | None = None,
3104
- kf: float | None = None,
3105
- ka: float | None = None,
3106
- mu: float | None = None,
3107
- restitution: float | None = None,
3108
- is_solid: bool = True,
3109
- thickness: float | None = None,
3110
- has_ground_collision: bool = True,
3111
- has_shape_collision: bool = True,
3112
- collision_group: int = -1,
3113
- is_visible: bool = True,
3114
- ) -> int:
3115
- """Add a cylinder collision shape to a body.
3116
-
3117
- Args:
3118
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
3119
- pos: The location of the shape with respect to the parent frame
3120
- rot: The rotation of the shape with respect to the parent frame
3121
- radius: The radius of the cylinder
3122
- half_height: The half length of the cylinder along the up axis
3123
- up_axis: The axis along which the cylinder is aligned (0=x, 1=y, 2=z)
3124
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
3125
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
3126
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
3127
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
3128
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
3129
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
3130
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
3131
- is_solid: Whether the cylinder is solid or hollow
3132
- thickness: Thickness to use for computing inertia of a hollow cylinder, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
3133
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
3134
- has_shape_collision: If True, the shape will collide with other shapes
3135
- collision_group: The collision group of the shape
3136
- is_visible: Whether the cylinder is visible
3137
-
3138
- Note:
3139
- Cylinders are currently not supported in rigid body collision handling.
3140
-
3141
- Returns:
3142
- The index of the added shape
3143
-
3144
- """
3145
-
3146
- q = rot
3147
- sqh = math.sqrt(0.5)
3148
- if up_axis == 0:
3149
- q = wp.mul(rot, wp.quat(0.0, 0.0, -sqh, sqh))
3150
- elif up_axis == 2:
3151
- q = wp.mul(rot, wp.quat(sqh, 0.0, 0.0, sqh))
3152
-
3153
- return self._add_shape(
3154
- body,
3155
- wp.vec3(pos),
3156
- wp.quat(q),
3157
- GEO_CYLINDER,
3158
- wp.vec3(radius, half_height, 0.0),
3159
- None,
3160
- density,
3161
- ke,
3162
- kd,
3163
- kf,
3164
- ka,
3165
- mu,
3166
- restitution,
3167
- thickness,
3168
- is_solid,
3169
- has_ground_collision=has_ground_collision,
3170
- has_shape_collision=has_shape_collision,
3171
- collision_group=collision_group,
3172
- is_visible=is_visible,
3173
- )
3174
-
3175
- def add_shape_cone(
3176
- self,
3177
- body: int,
3178
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
3179
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
3180
- radius: float = 1.0,
3181
- half_height: float = 0.5,
3182
- up_axis: int = 1,
3183
- density: float | None = None,
3184
- ke: float | None = None,
3185
- kd: float | None = None,
3186
- kf: float | None = None,
3187
- ka: float | None = None,
3188
- mu: float | None = None,
3189
- restitution: float | None = None,
3190
- is_solid: bool = True,
3191
- thickness: float | None = None,
3192
- has_ground_collision: bool = True,
3193
- has_shape_collision: bool = True,
3194
- collision_group: int = -1,
3195
- is_visible: bool = True,
3196
- ) -> int:
3197
- """Add a cone collision shape to a body.
3198
-
3199
- Args:
3200
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
3201
- pos: The location of the shape with respect to the parent frame
3202
- rot: The rotation of the shape with respect to the parent frame
3203
- radius: The radius of the cone
3204
- half_height: The half length of the cone along the up axis
3205
- up_axis: The axis along which the cone is aligned (0=x, 1=y, 2=z)
3206
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
3207
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
3208
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
3209
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
3210
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
3211
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
3212
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
3213
- is_solid: Whether the cone is solid or hollow
3214
- thickness: Thickness to use for computing inertia of a hollow cone, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
3215
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
3216
- has_shape_collision: If True, the shape will collide with other shapes
3217
- collision_group: The collision group of the shape
3218
- is_visible: Whether the cone is visible
3219
-
3220
- Note:
3221
- Cones are currently not supported in rigid body collision handling.
3222
-
3223
- Returns:
3224
- The index of the added shape
3225
-
3226
- """
3227
-
3228
- q = rot
3229
- sqh = math.sqrt(0.5)
3230
- if up_axis == 0:
3231
- q = wp.mul(rot, wp.quat(0.0, 0.0, -sqh, sqh))
3232
- elif up_axis == 2:
3233
- q = wp.mul(rot, wp.quat(sqh, 0.0, 0.0, sqh))
3234
-
3235
- return self._add_shape(
3236
- body,
3237
- wp.vec3(pos),
3238
- wp.quat(q),
3239
- GEO_CONE,
3240
- wp.vec3(radius, half_height, 0.0),
3241
- None,
3242
- density,
3243
- ke,
3244
- kd,
3245
- kf,
3246
- ka,
3247
- mu,
3248
- restitution,
3249
- thickness,
3250
- is_solid,
3251
- has_ground_collision=has_ground_collision,
3252
- has_shape_collision=has_shape_collision,
3253
- collision_group=collision_group,
3254
- is_visible=is_visible,
3255
- )
3256
-
3257
- def add_shape_mesh(
3258
- self,
3259
- body: int,
3260
- pos: Vec3 | None = None,
3261
- rot: Quat | None = None,
3262
- mesh: Mesh | None = None,
3263
- scale: Vec3 | None = None,
3264
- density: float | None = None,
3265
- ke: float | None = None,
3266
- kd: float | None = None,
3267
- kf: float | None = None,
3268
- ka: float | None = None,
3269
- mu: float | None = None,
3270
- restitution: float | None = None,
3271
- is_solid: bool = True,
3272
- thickness: float | None = None,
3273
- has_ground_collision: bool = True,
3274
- has_shape_collision: bool = True,
3275
- collision_group: int = -1,
3276
- is_visible: bool = True,
3277
- ) -> int:
3278
- """Add a triangle mesh collision shape to a body.
3279
-
3280
- Args:
3281
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
3282
- pos: The location of the shape with respect to the parent frame
3283
- (None to use the default value ``wp.vec3(0.0, 0.0, 0.0)``)
3284
- rot: The rotation of the shape with respect to the parent frame
3285
- (None to use the default value ``wp.quat(0.0, 0.0, 0.0, 1.0)``)
3286
- mesh: The mesh object
3287
- scale: Scale to use for the collider. (None to use the default value ``wp.vec3(1.0, 1.0, 1.0)``)
3288
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
3289
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
3290
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
3291
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
3292
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
3293
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
3294
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
3295
- is_solid: If True, the mesh is solid, otherwise it is a hollow surface with the given wall thickness
3296
- thickness: Thickness to use for computing inertia of a hollow mesh, and for collision handling (None to use the default value :attr:`default_shape_thickness`)
3297
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
3298
- has_shape_collision: If True, the shape will collide with other shapes
3299
- collision_group: The collision group of the shape
3300
- is_visible: Whether the mesh is visible
3301
-
3302
- Returns:
3303
- The index of the added shape
3304
-
3305
- """
3306
-
3307
- if pos is None:
3308
- pos = wp.vec3(0.0, 0.0, 0.0)
3309
-
3310
- if rot is None:
3311
- rot = wp.quat(0.0, 0.0, 0.0, 1.0)
3312
-
3313
- if scale is None:
3314
- scale = wp.vec3(1.0, 1.0, 1.0)
3315
-
3316
- return self._add_shape(
3317
- body,
3318
- pos,
3319
- rot,
3320
- GEO_MESH,
3321
- wp.vec3(scale[0], scale[1], scale[2]),
3322
- mesh,
3323
- density,
3324
- ke,
3325
- kd,
3326
- kf,
3327
- ka,
3328
- mu,
3329
- restitution,
3330
- thickness,
3331
- is_solid,
3332
- has_ground_collision=has_ground_collision,
3333
- has_shape_collision=has_shape_collision,
3334
- collision_group=collision_group,
3335
- is_visible=is_visible,
3336
- )
3337
-
3338
- def add_shape_sdf(
3339
- self,
3340
- body: int,
3341
- pos: Vec3 | tuple[float, float, float] = (0.0, 0.0, 0.0),
3342
- rot: Quat | tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
3343
- sdf: SDF | None = None,
3344
- scale: Vec3 | tuple[float, float, float] = (1.0, 1.0, 1.0),
3345
- density: float | None = None,
3346
- ke: float | None = None,
3347
- kd: float | None = None,
3348
- kf: float | None = None,
3349
- ka: float | None = None,
3350
- mu: float | None = None,
3351
- restitution: float | None = None,
3352
- is_solid: bool = True,
3353
- thickness: float | None = None,
3354
- has_ground_collision: bool = True,
3355
- has_shape_collision: bool = True,
3356
- collision_group: int = -1,
3357
- is_visible: bool = True,
3358
- ) -> int:
3359
- """Add a SDF collision shape to a body.
3360
-
3361
- Args:
3362
- body: The index of the parent body this shape belongs to (use -1 for static shapes)
3363
- pos: The location of the shape with respect to the parent frame
3364
- rot: The rotation of the shape with respect to the parent frame
3365
- sdf: The sdf object
3366
- scale: Scale to use for the collider
3367
- density: The density of the shape (None to use the default value :attr:`default_shape_density`)
3368
- ke: The contact elastic stiffness (None to use the default value :attr:`default_shape_ke`)
3369
- kd: The contact damping stiffness (None to use the default value :attr:`default_shape_kd`)
3370
- kf: The contact friction stiffness (None to use the default value :attr:`default_shape_kf`)
3371
- ka: The contact adhesion distance (None to use the default value :attr:`default_shape_ka`)
3372
- mu: The coefficient of friction (None to use the default value :attr:`default_shape_mu`)
3373
- restitution: The coefficient of restitution (None to use the default value :attr:`default_shape_restitution`)
3374
- is_solid: If True, the SDF is solid, otherwise it is a hollow surface with the given wall thickness
3375
- thickness: Thickness to use for collision handling (None to use the default value :attr:`default_shape_thickness`)
3376
- has_ground_collision: If True, the shape will collide with the ground plane if `Model.ground` is True
3377
- has_shape_collision: If True, the shape will collide with other shapes
3378
- collision_group: The collision group of the shape
3379
- is_visible: Whether the shape is visible
3380
-
3381
- Returns:
3382
- The index of the added shape
3383
-
3384
- """
3385
- return self._add_shape(
3386
- body,
3387
- wp.vec3(pos),
3388
- wp.quat(rot),
3389
- GEO_SDF,
3390
- wp.vec3(scale[0], scale[1], scale[2]),
3391
- sdf,
3392
- density,
3393
- ke,
3394
- kd,
3395
- kf,
3396
- ka,
3397
- mu,
3398
- restitution,
3399
- thickness,
3400
- is_solid,
3401
- has_ground_collision=has_ground_collision,
3402
- has_shape_collision=has_shape_collision,
3403
- collision_group=collision_group,
3404
- is_visible=is_visible,
3405
- )
3406
-
3407
- def _shape_radius(self, type, scale, src):
3408
- """
3409
- Calculates the radius of a sphere that encloses the shape, used for broadphase collision detection.
3410
- """
3411
- if type == GEO_SPHERE:
3412
- return scale[0]
3413
- elif type == GEO_BOX:
3414
- return np.linalg.norm(scale)
3415
- elif type == GEO_CAPSULE or type == GEO_CYLINDER or type == GEO_CONE:
3416
- return scale[0] + scale[1]
3417
- elif type == GEO_MESH:
3418
- vmax = np.max(np.abs(src.vertices), axis=0) * np.max(scale)
3419
- return np.linalg.norm(vmax)
3420
- elif type == GEO_PLANE:
3421
- if scale[0] > 0.0 and scale[1] > 0.0:
3422
- # finite plane
3423
- return np.linalg.norm(scale)
3424
- else:
3425
- return 1.0e6
3426
- else:
3427
- return 10.0
3428
-
3429
- def _add_shape(
3430
- self,
3431
- body,
3432
- pos,
3433
- rot,
3434
- type,
3435
- scale,
3436
- src=None,
3437
- density=None,
3438
- ke=None,
3439
- kd=None,
3440
- kf=None,
3441
- ka=None,
3442
- mu=None,
3443
- restitution=None,
3444
- thickness=None,
3445
- is_solid=True,
3446
- collision_group=-1,
3447
- collision_filter_parent=True,
3448
- has_ground_collision=True,
3449
- has_shape_collision=True,
3450
- is_visible: bool = True,
3451
- ) -> int:
3452
- self.shape_body.append(body)
3453
- shape = self.shape_count
3454
- if body in self.body_shapes:
3455
- # no contacts between shapes of the same body
3456
- for same_body_shape in self.body_shapes[body]:
3457
- self.shape_collision_filter_pairs.add((same_body_shape, shape))
3458
- self.body_shapes[body].append(shape)
3459
- else:
3460
- self.body_shapes[body] = [shape]
3461
- ke = ke if ke is not None else self.default_shape_ke
3462
- kd = kd if kd is not None else self.default_shape_kd
3463
- kf = kf if kf is not None else self.default_shape_kf
3464
- ka = ka if ka is not None else self.default_shape_ka
3465
- mu = mu if mu is not None else self.default_shape_mu
3466
- restitution = restitution if restitution is not None else self.default_shape_restitution
3467
- thickness = thickness if thickness is not None else self.default_shape_thickness
3468
- density = density if density is not None else self.default_shape_density
3469
- self.shape_transform.append(wp.transform(pos, rot))
3470
- self.shape_visible.append(is_visible)
3471
- self.shape_geo_type.append(type)
3472
- self.shape_geo_scale.append((scale[0], scale[1], scale[2]))
3473
- self.shape_geo_src.append(src)
3474
- self.shape_geo_thickness.append(thickness)
3475
- self.shape_geo_is_solid.append(is_solid)
3476
- self.shape_material_ke.append(ke)
3477
- self.shape_material_kd.append(kd)
3478
- self.shape_material_kf.append(kf)
3479
- self.shape_material_ka.append(ka)
3480
- self.shape_material_mu.append(mu)
3481
- self.shape_material_restitution.append(restitution)
3482
- self.shape_collision_group.append(collision_group)
3483
- if collision_group not in self.shape_collision_group_map:
3484
- self.shape_collision_group_map[collision_group] = []
3485
- self.last_collision_group = max(self.last_collision_group, collision_group)
3486
- self.shape_collision_group_map[collision_group].append(shape)
3487
- self.shape_collision_radius.append(self._shape_radius(type, scale, src))
3488
- if collision_filter_parent and body > -1 and body in self.joint_parents:
3489
- for parent_body in self.joint_parents[body]:
3490
- if parent_body > -1:
3491
- for parent_shape in self.body_shapes[parent_body]:
3492
- self.shape_collision_filter_pairs.add((parent_shape, shape))
3493
- if body == -1:
3494
- has_ground_collision = False
3495
- self.shape_ground_collision.append(has_ground_collision)
3496
- self.shape_shape_collision.append(has_shape_collision)
3497
-
3498
- if density > 0.0:
3499
- (m, c, I) = compute_shape_mass(type, scale, src, density, is_solid, thickness)
3500
- com_body = wp.transform_point(wp.transform(pos, rot), c)
3501
- self._update_body_mass(body, m, I, com_body, rot)
3502
- return shape
3503
-
3504
- # particles
3505
- def add_particle(
3506
- self,
3507
- pos: Vec3,
3508
- vel: Vec3,
3509
- mass: float,
3510
- radius: float | None = None,
3511
- flags: wp.uint32 = PARTICLE_FLAG_ACTIVE,
3512
- ) -> int:
3513
- """Adds a single particle to the model
3514
-
3515
- Args:
3516
- pos: The initial position of the particle
3517
- vel: The initial velocity of the particle
3518
- mass: The mass of the particle
3519
- radius: The radius of the particle used in collision handling. If None, the radius is set to the default value (:attr:`default_particle_radius`).
3520
- flags: The flags that control the dynamical behavior of the particle, see PARTICLE_FLAG_* constants
3521
-
3522
- Note:
3523
- Set the mass equal to zero to create a 'kinematic' particle that does is not subject to dynamics.
3524
-
3525
- Returns:
3526
- The index of the particle in the system
3527
- """
3528
- self.particle_q.append(pos)
3529
- self.particle_qd.append(vel)
3530
- self.particle_mass.append(mass)
3531
- if radius is None:
3532
- radius = self.default_particle_radius
3533
- self.particle_radius.append(radius)
3534
- self.particle_flags.append(flags)
3535
-
3536
- particle_id = self.particle_count - 1
3537
-
3538
- return particle_id
3539
-
3540
- def add_spring(self, i: int, j, ke: float, kd: float, control: float):
3541
- """Adds a spring between two particles in the system
3542
-
3543
- Args:
3544
- i: The index of the first particle
3545
- j: The index of the second particle
3546
- ke: The elastic stiffness of the spring
3547
- kd: The damping stiffness of the spring
3548
- control: The actuation level of the spring
3549
-
3550
- Note:
3551
- The spring is created with a rest-length based on the distance
3552
- between the particles in their initial configuration.
3553
-
3554
- """
3555
- self.spring_indices.append(i)
3556
- self.spring_indices.append(j)
3557
- self.spring_stiffness.append(ke)
3558
- self.spring_damping.append(kd)
3559
- self.spring_control.append(control)
3560
-
3561
- # compute rest length
3562
- p = self.particle_q[i]
3563
- q = self.particle_q[j]
3564
-
3565
- delta = np.subtract(p, q)
3566
- l = np.sqrt(np.dot(delta, delta))
3567
-
3568
- self.spring_rest_length.append(l)
3569
-
3570
- def add_triangle(
3571
- self,
3572
- i: int,
3573
- j: int,
3574
- k: int,
3575
- tri_ke: float | None = None,
3576
- tri_ka: float | None = None,
3577
- tri_kd: float | None = None,
3578
- tri_drag: float | None = None,
3579
- tri_lift: float | None = None,
3580
- ) -> float:
3581
- """Adds a triangular FEM element between three particles in the system.
3582
-
3583
- Triangles are modeled as viscoelastic elements with elastic stiffness and damping
3584
- parameters specified on the model. See model.tri_ke, model.tri_kd.
3585
-
3586
- Args:
3587
- i: The index of the first particle
3588
- j: The index of the second particle
3589
- k: The index of the third particle
3590
-
3591
- Return:
3592
- The area of the triangle
3593
-
3594
- Note:
3595
- The triangle is created with a rest-length based on the distance
3596
- between the particles in their initial configuration.
3597
- """
3598
- # TODO: Expose elastic parameters on a per-element basis
3599
- tri_ke = tri_ke if tri_ke is not None else self.default_tri_ke
3600
- tri_ka = tri_ka if tri_ka is not None else self.default_tri_ka
3601
- tri_kd = tri_kd if tri_kd is not None else self.default_tri_kd
3602
- tri_drag = tri_drag if tri_drag is not None else self.default_tri_drag
3603
- tri_lift = tri_lift if tri_lift is not None else self.default_tri_lift
3604
-
3605
- # compute basis for 2D rest pose
3606
- p = self.particle_q[i]
3607
- q = self.particle_q[j]
3608
- r = self.particle_q[k]
3609
-
3610
- qp = q - p
3611
- rp = r - p
3612
-
3613
- # construct basis aligned with the triangle
3614
- n = wp.normalize(wp.cross(qp, rp))
3615
- e1 = wp.normalize(qp)
3616
- e2 = wp.normalize(wp.cross(n, e1))
3617
-
3618
- R = np.array((e1, e2))
3619
- M = np.array((qp, rp))
3620
-
3621
- D = R @ M.T
3622
-
3623
- area = np.linalg.det(D) / 2.0
3624
-
3625
- if area <= 0.0:
3626
- print("inverted or degenerate triangle element")
3627
- return 0.0
3628
- else:
3629
- inv_D = np.linalg.inv(D)
3630
-
3631
- self.tri_indices.append((i, j, k))
3632
- self.tri_poses.append(inv_D.tolist())
3633
- self.tri_activations.append(0.0)
3634
- self.tri_materials.append((tri_ke, tri_ka, tri_kd, tri_drag, tri_lift))
3635
- self.tri_areas.append(area)
3636
- return area
3637
-
3638
- def add_triangles(
3639
- self,
3640
- i: list[int],
3641
- j: list[int],
3642
- k: list[int],
3643
- tri_ke: list[float] | None = None,
3644
- tri_ka: list[float] | None = None,
3645
- tri_kd: list[float] | None = None,
3646
- tri_drag: list[float] | None = None,
3647
- tri_lift: list[float] | None = None,
3648
- ) -> list[float]:
3649
- """Adds triangular FEM elements between groups of three particles in the system.
3650
-
3651
- Triangles are modeled as viscoelastic elements with elastic stiffness and damping
3652
- Parameters specified on the model. See model.tri_ke, model.tri_kd.
3653
-
3654
- Args:
3655
- i: The indices of the first particle
3656
- j: The indices of the second particle
3657
- k: The indices of the third particle
3658
-
3659
- Return:
3660
- The areas of the triangles
3661
-
3662
- Note:
3663
- A triangle is created with a rest-length based on the distance
3664
- between the particles in their initial configuration.
3665
-
3666
- """
3667
- # compute basis for 2D rest pose
3668
- p = np.array(self.particle_q)[i]
3669
- q = np.array(self.particle_q)[j]
3670
- r = np.array(self.particle_q)[k]
3671
-
3672
- qp = q - p
3673
- rp = r - p
3674
-
3675
- def normalized(a):
3676
- l = np.linalg.norm(a, axis=-1, keepdims=True)
3677
- l[l == 0] = 1.0
3678
- return a / l
3679
-
3680
- n = normalized(np.cross(qp, rp))
3681
- e1 = normalized(qp)
3682
- e2 = normalized(np.cross(n, e1))
3683
-
3684
- R = np.concatenate((e1[..., None], e2[..., None]), axis=-1)
3685
- M = np.concatenate((qp[..., None], rp[..., None]), axis=-1)
3686
-
3687
- D = np.matmul(R.transpose(0, 2, 1), M)
3688
-
3689
- areas = np.linalg.det(D) / 2.0
3690
- areas[areas < 0.0] = 0.0
3691
- valid_inds = (areas > 0.0).nonzero()[0]
3692
- if len(valid_inds) < len(areas):
3693
- print("inverted or degenerate triangle elements")
3694
-
3695
- D[areas == 0.0] = np.eye(2)[None, ...]
3696
- inv_D = np.linalg.inv(D)
3697
-
3698
- inds = np.concatenate((i[valid_inds, None], j[valid_inds, None], k[valid_inds, None]), axis=-1)
3699
-
3700
- self.tri_indices.extend(inds.tolist())
3701
- self.tri_poses.extend(inv_D[valid_inds].tolist())
3702
- self.tri_activations.extend([0.0] * len(valid_inds))
3703
-
3704
- def init_if_none(arr, defaultValue):
3705
- if arr is None:
3706
- return [defaultValue] * len(areas)
3707
- return arr
3708
-
3709
- tri_ke = init_if_none(tri_ke, self.default_tri_ke)
3710
- tri_ka = init_if_none(tri_ka, self.default_tri_ka)
3711
- tri_kd = init_if_none(tri_kd, self.default_tri_kd)
3712
- tri_drag = init_if_none(tri_drag, self.default_tri_drag)
3713
- tri_lift = init_if_none(tri_lift, self.default_tri_lift)
3714
-
3715
- self.tri_materials.extend(
3716
- zip(
3717
- np.array(tri_ke)[valid_inds],
3718
- np.array(tri_ka)[valid_inds],
3719
- np.array(tri_kd)[valid_inds],
3720
- np.array(tri_drag)[valid_inds],
3721
- np.array(tri_lift)[valid_inds],
3722
- )
3723
- )
3724
- areas = areas.tolist()
3725
- self.tri_areas.extend(areas)
3726
- return areas
3727
-
3728
- def add_tetrahedron(
3729
- self, i: int, j: int, k: int, l: int, k_mu: float = 1.0e3, k_lambda: float = 1.0e3, k_damp: float = 0.0
3730
- ) -> float:
3731
- """Adds a tetrahedral FEM element between four particles in the system.
3732
-
3733
- Tetrahedra are modeled as viscoelastic elements with a NeoHookean energy
3734
- density based on [Smith et al. 2018].
3735
-
3736
- Args:
3737
- i: The index of the first particle
3738
- j: The index of the second particle
3739
- k: The index of the third particle
3740
- l: The index of the fourth particle
3741
- k_mu: The first elastic Lame parameter
3742
- k_lambda: The second elastic Lame parameter
3743
- k_damp: The element's damping stiffness
3744
-
3745
- Return:
3746
- The volume of the tetrahedron
3747
-
3748
- Note:
3749
- The tetrahedron is created with a rest-pose based on the particle's initial configuration
3750
-
3751
- """
3752
- # compute basis for 2D rest pose
3753
- p = np.array(self.particle_q[i])
3754
- q = np.array(self.particle_q[j])
3755
- r = np.array(self.particle_q[k])
3756
- s = np.array(self.particle_q[l])
3757
-
3758
- qp = q - p
3759
- rp = r - p
3760
- sp = s - p
3761
-
3762
- Dm = np.array((qp, rp, sp)).T
3763
- volume = np.linalg.det(Dm) / 6.0
3764
-
3765
- if volume <= 0.0:
3766
- print("inverted tetrahedral element")
3767
- else:
3768
- inv_Dm = np.linalg.inv(Dm)
3769
-
3770
- self.tet_indices.append((i, j, k, l))
3771
- self.tet_poses.append(inv_Dm.tolist())
3772
- self.tet_activations.append(0.0)
3773
- self.tet_materials.append((k_mu, k_lambda, k_damp))
3774
-
3775
- return volume
3776
-
3777
- def add_edge(
3778
- self,
3779
- i: int,
3780
- j: int,
3781
- k: int,
3782
- l: int,
3783
- rest: float | None = None,
3784
- edge_ke: float | None = None,
3785
- edge_kd: float | None = None,
3786
- ) -> None:
3787
- """Adds a bending edge element between four particles in the system.
3788
-
3789
- Bending elements are designed to be between two connected triangles. Then
3790
- bending energy is based of [Bridson et al. 2002]. Bending stiffness is controlled
3791
- by the `model.tri_kb` parameter.
3792
-
3793
- Args:
3794
- i: The index of the first particle, i.e., opposite vertex 0
3795
- j: The index of the second particle, i.e., opposite vertex 1
3796
- k: The index of the third particle, i.e., vertex 0
3797
- l: The index of the fourth particle, i.e., vertex 1
3798
- rest: The rest angle across the edge in radians, if not specified it will be computed
3799
-
3800
- Note:
3801
- The edge lies between the particles indexed by 'k' and 'l' parameters with the opposing
3802
- vertices indexed by 'i' and 'j'. This defines two connected triangles with counter clockwise
3803
- winding: (i, k, l), (j, l, k).
3804
-
3805
- """
3806
- edge_ke = edge_ke if edge_ke is not None else self.default_edge_ke
3807
- edge_kd = edge_kd if edge_kd is not None else self.default_edge_kd
3808
-
3809
- # compute rest angle
3810
- x3 = self.particle_q[k]
3811
- x4 = self.particle_q[l]
3812
- if rest is None:
3813
- x1 = self.particle_q[i]
3814
- x2 = self.particle_q[j]
3815
-
3816
- n1 = wp.normalize(wp.cross(x3 - x1, x4 - x1))
3817
- n2 = wp.normalize(wp.cross(x4 - x2, x3 - x2))
3818
- e = wp.normalize(x4 - x3)
3819
-
3820
- d = np.clip(np.dot(n2, n1), -1.0, 1.0)
3821
-
3822
- angle = math.acos(d)
3823
- sign = np.sign(np.dot(np.cross(n2, n1), e))
3824
-
3825
- rest = angle * sign
3826
-
3827
- self.edge_indices.append((i, j, k, l))
3828
- self.edge_rest_angle.append(rest)
3829
- self.edge_rest_length.append(wp.length(x4 - x3))
3830
- self.edge_bending_properties.append((edge_ke, edge_kd))
3831
-
3832
- def add_edges(
3833
- self,
3834
- i,
3835
- j,
3836
- k,
3837
- l,
3838
- rest: list[float] | None = None,
3839
- edge_ke: list[float] | None = None,
3840
- edge_kd: list[float] | None = None,
3841
- ) -> None:
3842
- """Adds bending edge elements between groups of four particles in the system.
3843
-
3844
- Bending elements are designed to be between two connected triangles. Then
3845
- bending energy is based of [Bridson et al. 2002]. Bending stiffness is controlled
3846
- by the `model.tri_kb` parameter.
3847
-
3848
- Args:
3849
- i: The index of the first particle, i.e., opposite vertex 0
3850
- j: The index of the second particle, i.e., opposite vertex 1
3851
- k: The index of the third particle, i.e., vertex 0
3852
- l: The index of the fourth particle, i.e., vertex 1
3853
- rest: The rest angles across the edges in radians, if not specified they will be computed
3854
-
3855
- Note:
3856
- The edge lies between the particles indexed by 'k' and 'l' parameters with the opposing
3857
- vertices indexed by 'i' and 'j'. This defines two connected triangles with counter clockwise
3858
- winding: (i, k, l), (j, l, k).
3859
-
3860
- """
3861
- x3 = np.array(self.particle_q)[k]
3862
- x4 = np.array(self.particle_q)[l]
3863
- if rest is None:
3864
- # compute rest angle
3865
- x1 = np.array(self.particle_q)[i]
3866
- x2 = np.array(self.particle_q)[j]
3867
- x3 = np.array(self.particle_q)[k]
3868
- x4 = np.array(self.particle_q)[l]
3869
-
3870
- def normalized(a):
3871
- l = np.linalg.norm(a, axis=-1, keepdims=True)
3872
- l[l == 0] = 1.0
3873
- return a / l
3874
-
3875
- n1 = normalized(np.cross(x3 - x1, x4 - x1))
3876
- n2 = normalized(np.cross(x4 - x2, x3 - x2))
3877
- e = normalized(x4 - x3)
3878
-
3879
- def dot(a, b):
3880
- return (a * b).sum(axis=-1)
3881
-
3882
- d = np.clip(dot(n2, n1), -1.0, 1.0)
3883
-
3884
- angle = np.arccos(d)
3885
- sign = np.sign(dot(np.cross(n2, n1), e))
3886
-
3887
- rest = angle * sign
3888
-
3889
- inds = np.concatenate((i[:, None], j[:, None], k[:, None], l[:, None]), axis=-1)
3890
-
3891
- self.edge_indices.extend(inds.tolist())
3892
- self.edge_rest_angle.extend(rest.tolist())
3893
- self.edge_rest_length.extend(np.linalg.norm(x4 - x3, axis=1).tolist())
3894
-
3895
- def init_if_none(arr, defaultValue):
3896
- if arr is None:
3897
- return [defaultValue] * len(i)
3898
- return arr
3899
-
3900
- edge_ke = init_if_none(edge_ke, self.default_edge_ke)
3901
- edge_kd = init_if_none(edge_kd, self.default_edge_kd)
3902
-
3903
- self.edge_bending_properties.extend(zip(edge_ke, edge_kd))
3904
-
3905
- def add_cloth_grid(
3906
- self,
3907
- pos: Vec3,
3908
- rot: Quat,
3909
- vel: Vec3,
3910
- dim_x: int,
3911
- dim_y: int,
3912
- cell_x: float,
3913
- cell_y: float,
3914
- mass: float,
3915
- reverse_winding: bool = False,
3916
- fix_left: bool = False,
3917
- fix_right: bool = False,
3918
- fix_top: bool = False,
3919
- fix_bottom: bool = False,
3920
- tri_ke: float | None = None,
3921
- tri_ka: float | None = None,
3922
- tri_kd: float | None = None,
3923
- tri_drag: float | None = None,
3924
- tri_lift: float | None = None,
3925
- edge_ke: float | None = None,
3926
- edge_kd: float | None = None,
3927
- add_springs: bool = False,
3928
- spring_ke: float | None = None,
3929
- spring_kd: float | None = None,
3930
- particle_radius: float | None = None,
3931
- ) -> None:
3932
- """Helper to create a regular planar cloth grid
3933
-
3934
- Creates a rectangular grid of particles with FEM triangles and bending elements
3935
- automatically.
3936
-
3937
- Args:
3938
- pos: The position of the cloth in world space
3939
- rot: The orientation of the cloth in world space
3940
- vel: The velocity of the cloth in world space
3941
- dim_x_: The number of rectangular cells along the x-axis
3942
- dim_y: The number of rectangular cells along the y-axis
3943
- cell_x: The width of each cell in the x-direction
3944
- cell_y: The width of each cell in the y-direction
3945
- mass: The mass of each particle
3946
- reverse_winding: Flip the winding of the mesh
3947
- fix_left: Make the left-most edge of particles kinematic (fixed in place)
3948
- fix_right: Make the right-most edge of particles kinematic
3949
- fix_top: Make the top-most edge of particles kinematic
3950
- fix_bottom: Make the bottom-most edge of particles kinematic
3951
- """
3952
- tri_ke = tri_ke if tri_ke is not None else self.default_tri_ke
3953
- tri_ka = tri_ka if tri_ka is not None else self.default_tri_ka
3954
- tri_kd = tri_kd if tri_kd is not None else self.default_tri_kd
3955
- tri_drag = tri_drag if tri_drag is not None else self.default_tri_drag
3956
- tri_lift = tri_lift if tri_lift is not None else self.default_tri_lift
3957
- edge_ke = edge_ke if edge_ke is not None else self.default_edge_ke
3958
- edge_kd = edge_kd if edge_kd is not None else self.default_edge_kd
3959
- spring_ke = spring_ke if spring_ke is not None else self.default_spring_ke
3960
- spring_kd = spring_kd if spring_kd is not None else self.default_spring_kd
3961
- particle_radius = particle_radius if particle_radius is not None else self.default_particle_radius
3962
-
3963
- def grid_index(x, y, dim_x):
3964
- return y * dim_x + x
3965
-
3966
- start_vertex = len(self.particle_q)
3967
- start_tri = len(self.tri_indices)
3968
-
3969
- for y in range(0, dim_y + 1):
3970
- for x in range(0, dim_x + 1):
3971
- g = wp.vec3(x * cell_x, y * cell_y, 0.0)
3972
- p = wp.quat_rotate(rot, g) + pos
3973
- m = mass
3974
-
3975
- particle_flag = PARTICLE_FLAG_ACTIVE
3976
-
3977
- if x == 0 and fix_left:
3978
- m = 0.0
3979
- particle_flag = wp.uint32(int(particle_flag) & ~int(PARTICLE_FLAG_ACTIVE))
3980
- elif x == dim_x and fix_right:
3981
- m = 0.0
3982
- particle_flag = wp.uint32(int(particle_flag) & ~int(PARTICLE_FLAG_ACTIVE))
3983
- elif y == 0 and fix_bottom:
3984
- m = 0.0
3985
- particle_flag = wp.uint32(int(particle_flag) & ~int(PARTICLE_FLAG_ACTIVE))
3986
- elif y == dim_y and fix_top:
3987
- m = 0.0
3988
- particle_flag = wp.uint32(int(particle_flag) & ~int(PARTICLE_FLAG_ACTIVE))
3989
-
3990
- self.add_particle(p, vel, m, flags=particle_flag, radius=particle_radius)
3991
-
3992
- if x > 0 and y > 0:
3993
- if reverse_winding:
3994
- tri1 = (
3995
- start_vertex + grid_index(x - 1, y - 1, dim_x + 1),
3996
- start_vertex + grid_index(x, y - 1, dim_x + 1),
3997
- start_vertex + grid_index(x, y, dim_x + 1),
3998
- )
3999
-
4000
- tri2 = (
4001
- start_vertex + grid_index(x - 1, y - 1, dim_x + 1),
4002
- start_vertex + grid_index(x, y, dim_x + 1),
4003
- start_vertex + grid_index(x - 1, y, dim_x + 1),
4004
- )
4005
-
4006
- self.add_triangle(*tri1, tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4007
- self.add_triangle(*tri2, tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4008
-
4009
- else:
4010
- tri1 = (
4011
- start_vertex + grid_index(x - 1, y - 1, dim_x + 1),
4012
- start_vertex + grid_index(x, y - 1, dim_x + 1),
4013
- start_vertex + grid_index(x - 1, y, dim_x + 1),
4014
- )
4015
-
4016
- tri2 = (
4017
- start_vertex + grid_index(x, y - 1, dim_x + 1),
4018
- start_vertex + grid_index(x, y, dim_x + 1),
4019
- start_vertex + grid_index(x - 1, y, dim_x + 1),
4020
- )
4021
-
4022
- self.add_triangle(*tri1, tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4023
- self.add_triangle(*tri2, tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4024
-
4025
- end_tri = len(self.tri_indices)
4026
-
4027
- # bending constraints, could create these explicitly for a grid but this
4028
- # is a good test of the adjacency structure
4029
- adj = wp.utils.MeshAdjacency(self.tri_indices[start_tri:end_tri], end_tri - start_tri)
4030
-
4031
- spring_indices = set()
4032
-
4033
- for _k, e in adj.edges.items():
4034
- self.add_edge(
4035
- e.o0, e.o1, e.v0, e.v1, edge_ke=edge_ke, edge_kd=edge_kd
4036
- ) # opposite 0, opposite 1, vertex 0, vertex 1
4037
-
4038
- # skip constraints open edges
4039
- spring_indices.add((min(e.v0, e.v1), max(e.v0, e.v1)))
4040
- if e.f0 != -1:
4041
- spring_indices.add((min(e.o0, e.v0), max(e.o0, e.v0)))
4042
- spring_indices.add((min(e.o0, e.v1), max(e.o0, e.v1)))
4043
- if e.f1 != -1:
4044
- spring_indices.add((min(e.o1, e.v0), max(e.o1, e.v0)))
4045
- spring_indices.add((min(e.o1, e.v1), max(e.o1, e.v1)))
4046
-
4047
- if e.f0 != -1 and e.f1 != -1:
4048
- spring_indices.add((min(e.o0, e.o1), max(e.o0, e.o1)))
4049
-
4050
- if add_springs:
4051
- for i, j in spring_indices:
4052
- self.add_spring(i, j, spring_ke, spring_kd, control=0.0)
4053
-
4054
- def add_cloth_mesh(
4055
- self,
4056
- pos: Vec3,
4057
- rot: Quat,
4058
- scale: float,
4059
- vel: Vec3,
4060
- vertices: list[Vec3],
4061
- indices: list[int],
4062
- density: float,
4063
- edge_callback=None,
4064
- face_callback=None,
4065
- tri_ke: float | None = None,
4066
- tri_ka: float | None = None,
4067
- tri_kd: float | None = None,
4068
- tri_drag: float | None = None,
4069
- tri_lift: float | None = None,
4070
- edge_ke: float | None = None,
4071
- edge_kd: float | None = None,
4072
- add_springs: bool = False,
4073
- spring_ke: float | None = None,
4074
- spring_kd: float | None = None,
4075
- particle_radius: float | None = None,
4076
- ) -> None:
4077
- """Helper to create a cloth model from a regular triangle mesh
4078
-
4079
- Creates one FEM triangle element and one bending element for every face
4080
- and edge in the input triangle mesh
4081
-
4082
- Args:
4083
- pos: The position of the cloth in world space
4084
- rot: The orientation of the cloth in world space
4085
- vel: The velocity of the cloth in world space
4086
- vertices: A list of vertex positions
4087
- indices: A list of triangle indices, 3 entries per-face
4088
- density: The density per-area of the mesh
4089
- edge_callback: A user callback when an edge is created
4090
- face_callback: A user callback when a face is created
4091
- particle_radius: The particle_radius which controls particle based collisions.
4092
- Note:
4093
-
4094
- The mesh should be two manifold.
4095
- """
4096
- tri_ke = tri_ke if tri_ke is not None else self.default_tri_ke
4097
- tri_ka = tri_ka if tri_ka is not None else self.default_tri_ka
4098
- tri_kd = tri_kd if tri_kd is not None else self.default_tri_kd
4099
- tri_drag = tri_drag if tri_drag is not None else self.default_tri_drag
4100
- tri_lift = tri_lift if tri_lift is not None else self.default_tri_lift
4101
- edge_ke = edge_ke if edge_ke is not None else self.default_edge_ke
4102
- edge_kd = edge_kd if edge_kd is not None else self.default_edge_kd
4103
- spring_ke = spring_ke if spring_ke is not None else self.default_spring_ke
4104
- spring_kd = spring_kd if spring_kd is not None else self.default_spring_kd
4105
- particle_radius = particle_radius if particle_radius is not None else self.default_particle_radius
4106
-
4107
- num_tris = int(len(indices) / 3)
4108
-
4109
- start_vertex = len(self.particle_q)
4110
- start_tri = len(self.tri_indices)
4111
-
4112
- # particles
4113
- for v in vertices:
4114
- p = wp.quat_rotate(rot, v * scale) + pos
4115
-
4116
- self.add_particle(p, vel, 0.0, radius=particle_radius)
4117
-
4118
- # triangles
4119
- inds = start_vertex + np.array(indices)
4120
- inds = inds.reshape(-1, 3)
4121
- areas = self.add_triangles(
4122
- inds[:, 0],
4123
- inds[:, 1],
4124
- inds[:, 2],
4125
- [tri_ke] * num_tris,
4126
- [tri_ka] * num_tris,
4127
- [tri_kd] * num_tris,
4128
- [tri_drag] * num_tris,
4129
- [tri_lift] * num_tris,
4130
- )
4131
-
4132
- for t in range(num_tris):
4133
- area = areas[t]
4134
-
4135
- self.particle_mass[inds[t, 0]] += density * area / 3.0
4136
- self.particle_mass[inds[t, 1]] += density * area / 3.0
4137
- self.particle_mass[inds[t, 2]] += density * area / 3.0
4138
-
4139
- end_tri = len(self.tri_indices)
4140
-
4141
- adj = wp.utils.MeshAdjacency(self.tri_indices[start_tri:end_tri], end_tri - start_tri)
4142
-
4143
- edge_indices = np.fromiter(
4144
- (x for e in adj.edges.values() for x in (e.o0, e.o1, e.v0, e.v1)),
4145
- int,
4146
- ).reshape(-1, 4)
4147
- self.add_edges(
4148
- edge_indices[:, 0],
4149
- edge_indices[:, 1],
4150
- edge_indices[:, 2],
4151
- edge_indices[:, 3],
4152
- edge_ke=[edge_ke] * len(edge_indices),
4153
- edge_kd=[edge_kd] * len(edge_indices),
4154
- )
4155
-
4156
- if add_springs:
4157
- spring_indices = set()
4158
- for i, j, k, l in edge_indices:
4159
- spring_indices.add((min(k, l), max(k, l)))
4160
- if i != -1:
4161
- spring_indices.add((min(i, k), max(i, k)))
4162
- spring_indices.add((min(i, l), max(i, l)))
4163
- if j != -1:
4164
- spring_indices.add((min(j, k), max(j, k)))
4165
- spring_indices.add((min(j, l), max(j, l)))
4166
- if i != -1 and j != -1:
4167
- spring_indices.add((min(i, j), max(i, j)))
4168
-
4169
- for i, j in spring_indices:
4170
- self.add_spring(i, j, spring_ke, spring_kd, control=0.0)
4171
-
4172
- def add_particle_grid(
4173
- self,
4174
- pos: Vec3,
4175
- rot: Quat,
4176
- vel: Vec3,
4177
- dim_x: int,
4178
- dim_y: int,
4179
- dim_z: int,
4180
- cell_x: float,
4181
- cell_y: float,
4182
- cell_z: float,
4183
- mass: float,
4184
- jitter: float,
4185
- radius_mean: float | None = None,
4186
- radius_std: float = 0.0,
4187
- ) -> None:
4188
- radius_mean = radius_mean if radius_mean is not None else self.default_particle_radius
4189
-
4190
- rng = np.random.default_rng(42)
4191
- for z in range(dim_z):
4192
- for y in range(dim_y):
4193
- for x in range(dim_x):
4194
- v = wp.vec3(x * cell_x, y * cell_y, z * cell_z)
4195
- m = mass
4196
-
4197
- p = wp.quat_rotate(rot, v) + pos + wp.vec3(rng.random(3) * jitter)
4198
-
4199
- if radius_std > 0.0:
4200
- r = radius_mean + rng.standard_normal() * radius_std
4201
- else:
4202
- r = radius_mean
4203
- self.add_particle(p, vel, m, r)
4204
-
4205
- def add_soft_grid(
4206
- self,
4207
- pos: Vec3,
4208
- rot: Quat,
4209
- vel: Vec3,
4210
- dim_x: int,
4211
- dim_y: int,
4212
- dim_z: int,
4213
- cell_x: float,
4214
- cell_y: float,
4215
- cell_z: float,
4216
- density: float,
4217
- k_mu: float,
4218
- k_lambda: float,
4219
- k_damp: float,
4220
- fix_left: bool = False,
4221
- fix_right: bool = False,
4222
- fix_top: bool = False,
4223
- fix_bottom: bool = False,
4224
- tri_ke: float | None = None,
4225
- tri_ka: float | None = None,
4226
- tri_kd: float | None = None,
4227
- tri_drag: float | None = None,
4228
- tri_lift: float | None = None,
4229
- ) -> None:
4230
- """Helper to create a rectangular tetrahedral FEM grid
4231
-
4232
- Creates a regular grid of FEM tetrahedra and surface triangles. Useful for example
4233
- to create beams and sheets. Each hexahedral cell is decomposed into 5
4234
- tetrahedral elements.
4235
-
4236
- Args:
4237
- pos: The position of the solid in world space
4238
- rot: The orientation of the solid in world space
4239
- vel: The velocity of the solid in world space
4240
- dim_x_: The number of rectangular cells along the x-axis
4241
- dim_y: The number of rectangular cells along the y-axis
4242
- dim_z: The number of rectangular cells along the z-axis
4243
- cell_x: The width of each cell in the x-direction
4244
- cell_y: The width of each cell in the y-direction
4245
- cell_z: The width of each cell in the z-direction
4246
- density: The density of each particle
4247
- k_mu: The first elastic Lame parameter
4248
- k_lambda: The second elastic Lame parameter
4249
- k_damp: The damping stiffness
4250
- fix_left: Make the left-most edge of particles kinematic (fixed in place)
4251
- fix_right: Make the right-most edge of particles kinematic
4252
- fix_top: Make the top-most edge of particles kinematic
4253
- fix_bottom: Make the bottom-most edge of particles kinematic
4254
- """
4255
- tri_ke = tri_ke if tri_ke is not None else self.default_tri_ke
4256
- tri_ka = tri_ka if tri_ka is not None else self.default_tri_ka
4257
- tri_kd = tri_kd if tri_kd is not None else self.default_tri_kd
4258
- tri_drag = tri_drag if tri_drag is not None else self.default_tri_drag
4259
- tri_lift = tri_lift if tri_lift is not None else self.default_tri_lift
4260
-
4261
- start_vertex = len(self.particle_q)
4262
-
4263
- mass = cell_x * cell_y * cell_z * density
4264
-
4265
- for z in range(dim_z + 1):
4266
- for y in range(dim_y + 1):
4267
- for x in range(dim_x + 1):
4268
- v = wp.vec3(x * cell_x, y * cell_y, z * cell_z)
4269
- m = mass
4270
-
4271
- if fix_left and x == 0:
4272
- m = 0.0
4273
-
4274
- if fix_right and x == dim_x:
4275
- m = 0.0
4276
-
4277
- if fix_top and y == dim_y:
4278
- m = 0.0
4279
-
4280
- if fix_bottom and y == 0:
4281
- m = 0.0
4282
-
4283
- p = wp.quat_rotate(rot, v) + pos
4284
-
4285
- self.add_particle(p, vel, m)
4286
-
4287
- # dict of open faces
4288
- faces = {}
4289
-
4290
- def add_face(i: int, j: int, k: int):
4291
- key = tuple(sorted((i, j, k)))
4292
-
4293
- if key not in faces:
4294
- faces[key] = (i, j, k)
4295
- else:
4296
- del faces[key]
4297
-
4298
- def add_tet(i: int, j: int, k: int, l: int):
4299
- self.add_tetrahedron(i, j, k, l, k_mu, k_lambda, k_damp)
4300
-
4301
- add_face(i, k, j)
4302
- add_face(j, k, l)
4303
- add_face(i, j, l)
4304
- add_face(i, l, k)
4305
-
4306
- def grid_index(x, y, z):
4307
- return (dim_x + 1) * (dim_y + 1) * z + (dim_x + 1) * y + x
4308
-
4309
- for z in range(dim_z):
4310
- for y in range(dim_y):
4311
- for x in range(dim_x):
4312
- v0 = grid_index(x, y, z) + start_vertex
4313
- v1 = grid_index(x + 1, y, z) + start_vertex
4314
- v2 = grid_index(x + 1, y, z + 1) + start_vertex
4315
- v3 = grid_index(x, y, z + 1) + start_vertex
4316
- v4 = grid_index(x, y + 1, z) + start_vertex
4317
- v5 = grid_index(x + 1, y + 1, z) + start_vertex
4318
- v6 = grid_index(x + 1, y + 1, z + 1) + start_vertex
4319
- v7 = grid_index(x, y + 1, z + 1) + start_vertex
4320
-
4321
- if (x & 1) ^ (y & 1) ^ (z & 1):
4322
- add_tet(v0, v1, v4, v3)
4323
- add_tet(v2, v3, v6, v1)
4324
- add_tet(v5, v4, v1, v6)
4325
- add_tet(v7, v6, v3, v4)
4326
- add_tet(v4, v1, v6, v3)
4327
-
4328
- else:
4329
- add_tet(v1, v2, v5, v0)
4330
- add_tet(v3, v0, v7, v2)
4331
- add_tet(v4, v7, v0, v5)
4332
- add_tet(v6, v5, v2, v7)
4333
- add_tet(v5, v2, v7, v0)
4334
-
4335
- # add triangles
4336
- for _k, v in faces.items():
4337
- self.add_triangle(v[0], v[1], v[2], tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4338
-
4339
- def add_soft_mesh(
4340
- self,
4341
- pos: Vec3,
4342
- rot: Quat,
4343
- scale: float,
4344
- vel: Vec3,
4345
- vertices: list[Vec3],
4346
- indices: list[int],
4347
- density: float,
4348
- k_mu: float,
4349
- k_lambda: float,
4350
- k_damp: float,
4351
- tri_ke: float | None = None,
4352
- tri_ka: float | None = None,
4353
- tri_kd: float | None = None,
4354
- tri_drag: float | None = None,
4355
- tri_lift: float | None = None,
4356
- ) -> None:
4357
- """Helper to create a tetrahedral model from an input tetrahedral mesh
4358
-
4359
- Args:
4360
- pos: The position of the solid in world space
4361
- rot: The orientation of the solid in world space
4362
- vel: The velocity of the solid in world space
4363
- vertices: A list of vertex positions, array of 3D points
4364
- indices: A list of tetrahedron indices, 4 entries per-element, flattened array
4365
- density: The density per-area of the mesh
4366
- k_mu: The first elastic Lame parameter
4367
- k_lambda: The second elastic Lame parameter
4368
- k_damp: The damping stiffness
4369
- """
4370
- tri_ke = tri_ke if tri_ke is not None else self.default_tri_ke
4371
- tri_ka = tri_ka if tri_ka is not None else self.default_tri_ka
4372
- tri_kd = tri_kd if tri_kd is not None else self.default_tri_kd
4373
- tri_drag = tri_drag if tri_drag is not None else self.default_tri_drag
4374
- tri_lift = tri_lift if tri_lift is not None else self.default_tri_lift
4375
-
4376
- num_tets = int(len(indices) / 4)
4377
-
4378
- start_vertex = len(self.particle_q)
4379
-
4380
- # dict of open faces
4381
- faces = {}
4382
-
4383
- def add_face(i, j, k):
4384
- key = tuple(sorted((i, j, k)))
4385
-
4386
- if key not in faces:
4387
- faces[key] = (i, j, k)
4388
- else:
4389
- del faces[key]
4390
-
4391
- pos = wp.vec3(pos[0], pos[1], pos[2])
4392
- # add particles
4393
- for v in vertices:
4394
- p = wp.quat_rotate(rot, wp.vec3(v[0], v[1], v[2]) * scale) + pos
4395
-
4396
- self.add_particle(p, vel, 0.0)
4397
-
4398
- # add tetrahedra
4399
- for t in range(num_tets):
4400
- v0 = start_vertex + indices[t * 4 + 0]
4401
- v1 = start_vertex + indices[t * 4 + 1]
4402
- v2 = start_vertex + indices[t * 4 + 2]
4403
- v3 = start_vertex + indices[t * 4 + 3]
4404
-
4405
- volume = self.add_tetrahedron(v0, v1, v2, v3, k_mu, k_lambda, k_damp)
4406
-
4407
- # distribute volume fraction to particles
4408
- if volume > 0.0:
4409
- self.particle_mass[v0] += density * volume / 4.0
4410
- self.particle_mass[v1] += density * volume / 4.0
4411
- self.particle_mass[v2] += density * volume / 4.0
4412
- self.particle_mass[v3] += density * volume / 4.0
4413
-
4414
- # build open faces
4415
- add_face(v0, v2, v1)
4416
- add_face(v1, v2, v3)
4417
- add_face(v0, v1, v3)
4418
- add_face(v0, v3, v2)
4419
-
4420
- # add triangles
4421
- for _k, v in faces.items():
4422
- try:
4423
- self.add_triangle(v[0], v[1], v[2], tri_ke, tri_ka, tri_kd, tri_drag, tri_lift)
4424
- except np.linalg.LinAlgError:
4425
- continue
4426
-
4427
- # incrementally updates rigid body mass with additional mass and inertia expressed at a local to the body
4428
- def _update_body_mass(self, i, m, I, p, q):
4429
- if i == -1:
4430
- return
4431
-
4432
- # find new COM
4433
- new_mass = self.body_mass[i] + m
4434
-
4435
- if new_mass == 0.0: # no mass
4436
- return
4437
-
4438
- new_com = (self.body_com[i] * self.body_mass[i] + p * m) / new_mass
4439
-
4440
- # shift inertia to new COM
4441
- com_offset = new_com - self.body_com[i]
4442
- shape_offset = new_com - p
4443
-
4444
- new_inertia = transform_inertia(
4445
- self.body_mass[i], self.body_inertia[i], com_offset, wp.quat_identity()
4446
- ) + transform_inertia(m, I, shape_offset, q)
4447
-
4448
- self.body_mass[i] = new_mass
4449
- self.body_inertia[i] = new_inertia
4450
- self.body_com[i] = new_com
4451
-
4452
- if new_mass > 0.0:
4453
- self.body_inv_mass[i] = 1.0 / new_mass
4454
- else:
4455
- self.body_inv_mass[i] = 0.0
4456
-
4457
- if any(x for x in new_inertia):
4458
- self.body_inv_inertia[i] = wp.inverse(new_inertia)
4459
- else:
4460
- self.body_inv_inertia[i] = new_inertia
4461
-
4462
- def set_ground_plane(
4463
- self,
4464
- normal=None,
4465
- offset=0.0,
4466
- ke: float | None = None,
4467
- kd: float | None = None,
4468
- kf: float | None = None,
4469
- mu: float | None = None,
4470
- restitution: float | None = None,
4471
- ) -> None:
4472
- """Create a ground plane for the world.
4473
-
4474
- If the normal is not specified, the ``up_vector`` of the :class:`ModelBuilder` is used.
4475
- """
4476
- ke = ke if ke is not None else self.default_shape_ke
4477
- kd = kd if kd is not None else self.default_shape_kd
4478
- kf = kf if kf is not None else self.default_shape_kf
4479
- mu = mu if mu is not None else self.default_shape_mu
4480
- restitution = restitution if restitution is not None else self.default_shape_restitution
4481
-
4482
- if normal is None:
4483
- normal = self.up_vector
4484
- self._ground_params = {
4485
- "plane": (*normal, offset),
4486
- "width": 0.0,
4487
- "length": 0.0,
4488
- "ke": ke,
4489
- "kd": kd,
4490
- "kf": kf,
4491
- "mu": mu,
4492
- "restitution": restitution,
4493
- }
4494
-
4495
- def _create_ground_plane(self) -> None:
4496
- ground_id = self.add_shape_plane(**self._ground_params)
4497
- self._ground_created = True
4498
- # disable ground collisions as they will be treated separately
4499
- for i in range(self.shape_count - 1):
4500
- self.shape_collision_filter_pairs.add((i, ground_id))
4501
-
4502
- def set_coloring(self, particle_color_groups):
4503
- """Set coloring information with user-provided coloring.
4504
-
4505
- Args:
4506
- particle_color_groups: A list of list or `np.array` with `dtype`=`int`. The length of the list is the number of colors
4507
- and each list or `np.array` contains the indices of vertices with this color.
4508
- """
4509
- particle_color_groups = [
4510
- color_group if isinstance(color_group, np.ndarray) else np.array(color_group)
4511
- for color_group in particle_color_groups
4512
- ]
4513
- self.particle_color_groups = particle_color_groups
4514
-
4515
- def color(
4516
- self,
4517
- include_bending=False,
4518
- balance_colors=True,
4519
- target_max_min_color_ratio=1.1,
4520
- coloring_algorithm=ColoringAlgorithm.MCS,
4521
- ) -> None:
4522
- """Run coloring algorithm to generate coloring information.
4523
-
4524
- Args:
4525
- include_bending_energy: Whether to consider bending energy for trimeshes in the coloring process. If set to `True`, the generated
4526
- graph will contain all the edges connecting o1 and o2; otherwise, the graph will be equivalent to the trimesh.
4527
- balance_colors: Whether to apply the color balancing algorithm to balance the size of each color
4528
- target_max_min_color_ratio: the color balancing algorithm will stop when the ratio between the largest color and
4529
- the smallest color reaches this value
4530
- algorithm: Value should be an enum type of ColoringAlgorithm, otherwise it will raise an error. ColoringAlgorithm.mcs means using the MCS coloring algorithm,
4531
- while ColoringAlgorithm.ordered_greedy means using the degree-ordered greedy algorithm. The MCS algorithm typically generates 30% to 50% fewer colors
4532
- compared to the ordered greedy algorithm, while maintaining the same linear complexity. Although MCS has a constant overhead that makes it about twice
4533
- as slow as the greedy algorithm, it produces significantly better coloring results. We recommend using MCS, especially if coloring is only part of the
4534
- preprocessing.
4535
-
4536
- Note:
4537
-
4538
- References to the coloring algorithm:
4539
-
4540
- MCS: Pereira, F. M. Q., & Palsberg, J. (2005, November). Register allocation via coloring of chordal graphs. In Asian Symposium on Programming Languages and Systems (pp. 315-329). Berlin, Heidelberg: Springer Berlin Heidelberg.
4541
-
4542
- Ordered Greedy: Ton-That, Q. M., Kry, P. G., & Andrews, S. (2023). Parallel block Neo-Hookean XPBD using graph clustering. Computers & Graphics, 110, 1-10.
4543
-
4544
- """
4545
- # ignore bending energy if it is too small
4546
- edge_indices = np.array(self.edge_indices)
4547
-
4548
- self.particle_color_groups = color_trimesh(
4549
- len(self.particle_q),
4550
- edge_indices,
4551
- include_bending,
4552
- algorithm=coloring_algorithm,
4553
- balance_colors=balance_colors,
4554
- target_max_min_color_ratio=target_max_min_color_ratio,
4555
- )
4556
-
4557
- def finalize(self, device=None, requires_grad=False) -> Model:
4558
- """Convert this builder object to a concrete model for simulation.
4559
-
4560
- After building simulation elements this method should be called to transfer
4561
- all data to device memory ready for simulation.
4562
-
4563
- Args:
4564
- device: The simulation device to use, e.g.: 'cpu', 'cuda'
4565
- requires_grad: Whether to enable gradient computation for the model
4566
-
4567
- Returns:
4568
-
4569
- A model object.
4570
- """
4571
-
4572
- # ensure the env count is set correctly
4573
- self.num_envs = max(1, self.num_envs)
4574
-
4575
- # add ground plane if not already created
4576
- if not self._ground_created:
4577
- self._create_ground_plane()
4578
-
4579
- # construct particle inv masses
4580
- ms = np.array(self.particle_mass, dtype=np.float32)
4581
- # static particles (with zero mass) have zero inverse mass
4582
- particle_inv_mass = np.divide(1.0, ms, out=np.zeros_like(ms), where=ms != 0.0)
4583
-
4584
- with wp.ScopedDevice(device):
4585
- # -------------------------------------
4586
- # construct Model (non-time varying) data
4587
-
4588
- m = Model(device)
4589
- m.requires_grad = requires_grad
4590
-
4591
- m.ground_plane_params = self._ground_params["plane"]
4592
-
4593
- m.num_envs = self.num_envs
4594
-
4595
- # ---------------------
4596
- # particles
4597
-
4598
- # state (initial)
4599
- m.particle_q = wp.array(self.particle_q, dtype=wp.vec3, requires_grad=requires_grad)
4600
- m.particle_qd = wp.array(self.particle_qd, dtype=wp.vec3, requires_grad=requires_grad)
4601
- m.particle_mass = wp.array(self.particle_mass, dtype=wp.float32, requires_grad=requires_grad)
4602
- m.particle_inv_mass = wp.array(particle_inv_mass, dtype=wp.float32, requires_grad=requires_grad)
4603
- m.particle_radius = wp.array(self.particle_radius, dtype=wp.float32, requires_grad=requires_grad)
4604
- m.particle_flags = wp.array([flag_to_int(f) for f in self.particle_flags], dtype=wp.uint32)
4605
- m.particle_max_radius = np.max(self.particle_radius) if len(self.particle_radius) > 0 else 0.0
4606
- m.particle_max_velocity = self.particle_max_velocity
4607
-
4608
- particle_colors = np.empty(self.particle_count, dtype=int)
4609
- for color in range(len(self.particle_color_groups)):
4610
- particle_colors[self.particle_color_groups[color]] = color
4611
- m.particle_colors = wp.array(particle_colors, dtype=int)
4612
- m.particle_color_groups = [wp.array(group, dtype=int) for group in self.particle_color_groups]
4613
-
4614
- # hash-grid for particle interactions
4615
- m.particle_grid = wp.HashGrid(128, 128, 128)
4616
-
4617
- # ---------------------
4618
- # collision geometry
4619
-
4620
- m.shape_transform = wp.array(self.shape_transform, dtype=wp.transform, requires_grad=requires_grad)
4621
- m.shape_body = wp.array(self.shape_body, dtype=wp.int32)
4622
- m.shape_visible = wp.array(self.shape_visible, dtype=wp.bool)
4623
- m.body_shapes = self.body_shapes
4624
-
4625
- # build list of ids for geometry sources (meshes, sdfs)
4626
- geo_sources = []
4627
- finalized_meshes = {} # do not duplicate meshes
4628
- for geo in self.shape_geo_src:
4629
- geo_hash = hash(geo) # avoid repeated hash computations
4630
- if geo:
4631
- if geo_hash not in finalized_meshes:
4632
- finalized_meshes[geo_hash] = geo.finalize(device=device)
4633
- geo_sources.append(finalized_meshes[geo_hash])
4634
- else:
4635
- # add null pointer
4636
- geo_sources.append(0)
4637
-
4638
- m.shape_geo.type = wp.array(self.shape_geo_type, dtype=wp.int32)
4639
- m.shape_geo.source = wp.array(geo_sources, dtype=wp.uint64)
4640
- m.shape_geo.scale = wp.array(self.shape_geo_scale, dtype=wp.vec3, requires_grad=requires_grad)
4641
- m.shape_geo.is_solid = wp.array(self.shape_geo_is_solid, dtype=wp.uint8)
4642
- m.shape_geo.thickness = wp.array(self.shape_geo_thickness, dtype=wp.float32, requires_grad=requires_grad)
4643
- m.shape_geo_src = self.shape_geo_src # used for rendering
4644
- # store refs to geometry
4645
- m.geo_meshes = self.geo_meshes
4646
- m.geo_sdfs = self.geo_sdfs
4647
-
4648
- m.shape_materials.ke = wp.array(self.shape_material_ke, dtype=wp.float32, requires_grad=requires_grad)
4649
- m.shape_materials.kd = wp.array(self.shape_material_kd, dtype=wp.float32, requires_grad=requires_grad)
4650
- m.shape_materials.kf = wp.array(self.shape_material_kf, dtype=wp.float32, requires_grad=requires_grad)
4651
- m.shape_materials.ka = wp.array(self.shape_material_ka, dtype=wp.float32, requires_grad=requires_grad)
4652
- m.shape_materials.mu = wp.array(self.shape_material_mu, dtype=wp.float32, requires_grad=requires_grad)
4653
- m.shape_materials.restitution = wp.array(
4654
- self.shape_material_restitution, dtype=wp.float32, requires_grad=requires_grad
4655
- )
4656
-
4657
- m.shape_collision_filter_pairs = self.shape_collision_filter_pairs
4658
- m.shape_collision_group = self.shape_collision_group
4659
- m.shape_collision_group_map = self.shape_collision_group_map
4660
- m.shape_collision_radius = wp.array(
4661
- self.shape_collision_radius, dtype=wp.float32, requires_grad=requires_grad
4662
- )
4663
- m.shape_ground_collision = self.shape_ground_collision
4664
- m.shape_shape_collision = self.shape_shape_collision
4665
-
4666
- # ---------------------
4667
- # springs
4668
-
4669
- m.spring_indices = wp.array(self.spring_indices, dtype=wp.int32)
4670
- m.spring_rest_length = wp.array(self.spring_rest_length, dtype=wp.float32, requires_grad=requires_grad)
4671
- m.spring_stiffness = wp.array(self.spring_stiffness, dtype=wp.float32, requires_grad=requires_grad)
4672
- m.spring_damping = wp.array(self.spring_damping, dtype=wp.float32, requires_grad=requires_grad)
4673
- m.spring_control = wp.array(self.spring_control, dtype=wp.float32, requires_grad=requires_grad)
4674
-
4675
- # ---------------------
4676
- # triangles
4677
-
4678
- m.tri_indices = wp.array(self.tri_indices, dtype=wp.int32)
4679
- m.tri_poses = wp.array(self.tri_poses, dtype=wp.mat22, requires_grad=requires_grad)
4680
- m.tri_activations = wp.array(self.tri_activations, dtype=wp.float32, requires_grad=requires_grad)
4681
- m.tri_materials = wp.array(self.tri_materials, dtype=wp.float32, requires_grad=requires_grad)
4682
- m.tri_areas = wp.array(self.tri_areas, dtype=wp.float32, requires_grad=requires_grad)
4683
-
4684
- # ---------------------
4685
- # edges
4686
-
4687
- m.edge_indices = wp.array(self.edge_indices, dtype=wp.int32)
4688
- m.edge_rest_angle = wp.array(self.edge_rest_angle, dtype=wp.float32, requires_grad=requires_grad)
4689
- m.edge_rest_length = wp.array(self.edge_rest_length, dtype=wp.float32, requires_grad=requires_grad)
4690
- m.edge_bending_properties = wp.array(
4691
- self.edge_bending_properties, dtype=wp.float32, requires_grad=requires_grad
4692
- )
4693
-
4694
- # ---------------------
4695
- # tetrahedra
4696
-
4697
- m.tet_indices = wp.array(self.tet_indices, dtype=wp.int32)
4698
- m.tet_poses = wp.array(self.tet_poses, dtype=wp.mat33, requires_grad=requires_grad)
4699
- m.tet_activations = wp.array(self.tet_activations, dtype=wp.float32, requires_grad=requires_grad)
4700
- m.tet_materials = wp.array(self.tet_materials, dtype=wp.float32, requires_grad=requires_grad)
4701
-
4702
- # -----------------------
4703
- # muscles
4704
-
4705
- # close the muscle waypoint indices
4706
- muscle_start = copy.copy(self.muscle_start)
4707
- muscle_start.append(len(self.muscle_bodies))
4708
-
4709
- m.muscle_start = wp.array(muscle_start, dtype=wp.int32)
4710
- m.muscle_params = wp.array(self.muscle_params, dtype=wp.float32, requires_grad=requires_grad)
4711
- m.muscle_bodies = wp.array(self.muscle_bodies, dtype=wp.int32)
4712
- m.muscle_points = wp.array(self.muscle_points, dtype=wp.vec3, requires_grad=requires_grad)
4713
- m.muscle_activations = wp.array(self.muscle_activations, dtype=wp.float32, requires_grad=requires_grad)
4714
-
4715
- # --------------------------------------
4716
- # rigid bodies
4717
-
4718
- m.body_q = wp.array(self.body_q, dtype=wp.transform, requires_grad=requires_grad)
4719
- m.body_qd = wp.array(self.body_qd, dtype=wp.spatial_vector, requires_grad=requires_grad)
4720
- m.body_inertia = wp.array(self.body_inertia, dtype=wp.mat33, requires_grad=requires_grad)
4721
- m.body_inv_inertia = wp.array(self.body_inv_inertia, dtype=wp.mat33, requires_grad=requires_grad)
4722
- m.body_mass = wp.array(self.body_mass, dtype=wp.float32, requires_grad=requires_grad)
4723
- m.body_inv_mass = wp.array(self.body_inv_mass, dtype=wp.float32, requires_grad=requires_grad)
4724
- m.body_com = wp.array(self.body_com, dtype=wp.vec3, requires_grad=requires_grad)
4725
- m.body_name = self.body_name
4726
-
4727
- # joints
4728
- m.joint_type = wp.array(self.joint_type, dtype=wp.int32)
4729
- m.joint_parent = wp.array(self.joint_parent, dtype=wp.int32)
4730
- m.joint_child = wp.array(self.joint_child, dtype=wp.int32)
4731
- m.joint_X_p = wp.array(self.joint_X_p, dtype=wp.transform, requires_grad=requires_grad)
4732
- m.joint_X_c = wp.array(self.joint_X_c, dtype=wp.transform, requires_grad=requires_grad)
4733
- m.joint_axis_start = wp.array(self.joint_axis_start, dtype=wp.int32)
4734
- m.joint_axis_dim = wp.array(np.array(self.joint_axis_dim), dtype=wp.int32, ndim=2)
4735
- m.joint_axis = wp.array(self.joint_axis, dtype=wp.vec3, requires_grad=requires_grad)
4736
- m.joint_q = wp.array(self.joint_q, dtype=wp.float32, requires_grad=requires_grad)
4737
- m.joint_qd = wp.array(self.joint_qd, dtype=wp.float32, requires_grad=requires_grad)
4738
- m.joint_name = self.joint_name
4739
- # compute joint ancestors
4740
- child_to_joint = {}
4741
- for i, child in enumerate(self.joint_child):
4742
- child_to_joint[child] = i
4743
- parent_joint = []
4744
- for parent in self.joint_parent:
4745
- parent_joint.append(child_to_joint.get(parent, -1))
4746
- m.joint_ancestor = wp.array(parent_joint, dtype=wp.int32)
4747
-
4748
- # dynamics properties
4749
- m.joint_armature = wp.array(self.joint_armature, dtype=wp.float32, requires_grad=requires_grad)
4750
- m.joint_target_ke = wp.array(self.joint_target_ke, dtype=wp.float32, requires_grad=requires_grad)
4751
- m.joint_target_kd = wp.array(self.joint_target_kd, dtype=wp.float32, requires_grad=requires_grad)
4752
- m.joint_axis_mode = wp.array(self.joint_axis_mode, dtype=wp.int32)
4753
- m.joint_act = wp.array(self.joint_act, dtype=wp.float32, requires_grad=requires_grad)
4754
-
4755
- m.joint_limit_lower = wp.array(self.joint_limit_lower, dtype=wp.float32, requires_grad=requires_grad)
4756
- m.joint_limit_upper = wp.array(self.joint_limit_upper, dtype=wp.float32, requires_grad=requires_grad)
4757
- m.joint_limit_ke = wp.array(self.joint_limit_ke, dtype=wp.float32, requires_grad=requires_grad)
4758
- m.joint_limit_kd = wp.array(self.joint_limit_kd, dtype=wp.float32, requires_grad=requires_grad)
4759
- m.joint_linear_compliance = wp.array(
4760
- self.joint_linear_compliance, dtype=wp.float32, requires_grad=requires_grad
4761
- )
4762
- m.joint_angular_compliance = wp.array(
4763
- self.joint_angular_compliance, dtype=wp.float32, requires_grad=requires_grad
4764
- )
4765
- m.joint_enabled = wp.array(self.joint_enabled, dtype=wp.int32)
4766
-
4767
- # 'close' the start index arrays with a sentinel value
4768
- joint_q_start = copy.copy(self.joint_q_start)
4769
- joint_q_start.append(self.joint_coord_count)
4770
- joint_qd_start = copy.copy(self.joint_qd_start)
4771
- joint_qd_start.append(self.joint_dof_count)
4772
- articulation_start = copy.copy(self.articulation_start)
4773
- articulation_start.append(self.joint_count)
4774
-
4775
- m.joint_q_start = wp.array(joint_q_start, dtype=wp.int32)
4776
- m.joint_qd_start = wp.array(joint_qd_start, dtype=wp.int32)
4777
- m.articulation_start = wp.array(articulation_start, dtype=wp.int32)
4778
-
4779
- # counts
4780
- m.joint_count = self.joint_count
4781
- m.joint_axis_count = self.joint_axis_count
4782
- m.joint_dof_count = self.joint_dof_count
4783
- m.joint_coord_count = self.joint_coord_count
4784
- m.particle_count = len(self.particle_q)
4785
- m.body_count = len(self.body_q)
4786
- m.shape_count = len(self.shape_geo_type)
4787
- m.tri_count = len(self.tri_poses)
4788
- m.tet_count = len(self.tet_poses)
4789
- m.edge_count = len(self.edge_rest_angle)
4790
- m.spring_count = len(self.spring_rest_length)
4791
- m.muscle_count = len(self.muscle_start)
4792
- m.articulation_count = len(self.articulation_start)
4793
-
4794
- # contacts
4795
- if m.particle_count:
4796
- m.allocate_soft_contacts(self.soft_contact_max, requires_grad=requires_grad)
4797
- m.find_shape_contact_pairs()
4798
- if self.num_rigid_contacts_per_env is None:
4799
- contact_count, limited_contact_count = m.count_contact_points()
4800
- else:
4801
- contact_count = limited_contact_count = self.num_rigid_contacts_per_env * self.num_envs
4802
- if contact_count:
4803
- if wp.config.verbose:
4804
- print(f"Allocating {contact_count} rigid contacts.")
4805
- m.allocate_rigid_contacts(
4806
- count=contact_count, limited_contact_count=limited_contact_count, requires_grad=requires_grad
4807
- )
4808
- m.rigid_mesh_contact_max = self.rigid_mesh_contact_max
4809
- m.rigid_contact_margin = self.rigid_contact_margin
4810
- m.rigid_contact_torsional_friction = self.rigid_contact_torsional_friction
4811
- m.rigid_contact_rolling_friction = self.rigid_contact_rolling_friction
4812
-
4813
- # enable ground plane
4814
- m.ground_plane = wp.array(self._ground_params["plane"], dtype=wp.float32, requires_grad=requires_grad)
4815
- m.gravity = np.array(self.up_vector, dtype=wp.float32) * self.gravity
4816
- m.up_axis = self.up_axis
4817
- m.up_vector = np.array(self.up_vector, dtype=wp.float32)
4818
-
4819
- m.enable_tri_collisions = False
4820
-
4821
- return m