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