warp-lang 1.7.0__py3-none-manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

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