| name | soft-body-simulation |
| description | 软体仿真技能 - MuJoCo/Isaac Gym 软体物理、绳索/布料/软体抓取、仿真训练迁移 |
| argument-hint | 软体仿真 OR soft body OR 绳索仿真 OR 布料仿真 OR MuJoCo |
| user-invocable | true |
软体仿真技能
用于实现软体物理仿真,涵盖绳索、布料、软体抓取和仿真-现实迁移(Sim2Real)
何时使用
当需要以下帮助时使用此技能:
- 仿真绳索、布料、果冻等软体
- 软体机器人抓取和操作
- MuJoCo/Isaac Gym 软体环境配置
- Sim2Real 迁移(domain randomization)
快速参考
软体类型
软体仿真:
├── 绳索 (Rope/Cable) → 1D柔性,细长物体
├── 布料 (Cloth/Sheet) → 2D柔性,平坦软体
├── 颗粒 (Granular) → 沙子、粉末
├── 体积软体 (Volumetric) → 果冻、海绵
└── 混合刚软 (Hybrid) → 刚体+软体组合
仿真器支持
pip install mujoco
pip install isaacgymenvs
pip install pybullet
MuJoCo 绳索仿真
绳索建模
import mujoco
import mujoco.viewer as viewer
import numpy as np
class RopeSimulator:
"""MuJoCo 绳索仿真"""
def __init__(self, num_segments: int = 20, rope_length: float = 1.0):
self.n = num_segments
self.dt = 0.002
self.model = self._build_model(num_segments, rope_length)
self.data = mujoco.MjData(self.model)
self.viewer = None
def _build_model(self, n: int, length: float) -> mujoco.Model:
"""构建绳索 MuJoCo 模型"""
segment_length = length / n
geom_radius = 0.01
xml = f"""
<mujoco model="rope">
<option timestep="{self.dt}" iterations="50" tolerance="1e-10">
<flag contact="enable" energy="enable"/>
</option>
<worldbody>
<!-- 固定端(世界坐标) -->
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
<!-- 绳索段 -->
<body name="seg0" pos="0 0 2">
<freejoint/>
<geom type="sphere" size="{geom_radius}" friction="0.5"/>
</body>
"""
for i in range(1, n):
xml += f"""
<body name="seg{i}" pos="{i * segment_length} 0 2">
<freejoint/>
<geom type="sphere" size="{geom_radius}" friction="0.5"/>
</body>
"""
for i in range(n - 1):
xml += f"""
<tendon>
<fixed name="tendon{i}" stiffness="1000" damping="1">
<joint joint="seg{i}_freejoint" coef1="1.0" coef2="-1.0"/>
</fixed>
</tendon>
"""
xml += """
</worldbody>
</mujoco>
"""
return mujoco.from_xml_string(xml)
def simulate(self, num_steps: int = 1000):
"""运行仿真"""
with viewer.launch_passive(self.model, self.data) as v:
for _ in range(num_steps):
mujoco.mj_step(self.model, self.data)
v.sync()
def apply_force(self, segment_id: int, force: np.ndarray):
"""对指定段施加力"""
self.data.xfrc_applied[segment_id + 1, :3] = force
def get_end_effector_position(self) -> np.ndarray:
"""获取末端位置"""
return self.data.body('seg{}'.format(self.n - 1)).xpos.copy()
class RopeManipulator:
"""绳索操作控制器"""
def __init__(self, rope_sim: RopeSimulator):
self.rope = rope_sim
self.Kp = 5.0
self.Kd = 2.0
def position_control(
self,
target: np.ndarray,
max_segments_to_move: int = 5
) -> np.ndarray:
"""
末端位置控制
Returns: 各段控制力
"""
current_end = self.rope.get_end_effector_position()
error = target - current_end
desired_velocity = self.Kp * error
current_velocity = self.rope.data.qvel[-3:]
forces = np.zeros(self.rope.n * 6)
for i in range(max_segments_to_move):
forces[i * 6:(i + 1) * 6] = (
self.Kp * error - self.Kd * current_velocity
) / max_segments_to_move
return forces
class CableRoutingEnv:
"""电缆布线环境"""
def __init__(self):
self.rope = RopeSimulator(num_segments=30, rope_length=1.5)
self.obstacles = []
self.reach_threshold = 0.02
self.collision_penalty = -1.0
self.success_reward = 10.0
def reset(self) -> np.ndarray:
"""重置环境"""
mujoco.mj_resetModel(self.rope.model, self.rope.data)
return self.rope.data.qpos.copy()
def step(self, action: np.ndarray) -> tuple:
"""
执行动作
Args:
action: 控制命令
Returns:
(next_state, reward, done, info)
"""
for i in range(len(action)):
self.rope.data.ctrl[i] = action[i]
mujoco.mj_step(self.rope.model, self.rope.data)
end_pos = self.rope.get_end_effector_position()
goal_pos = np.array([0.5, 0.0, 1.0])
dist_to_goal = np.linalg.norm(end_pos - goal_pos)
reward = -dist_to_goal
done = dist_to_goal < self.reach_threshold
if done:
reward += self.success_reward
return self.rope.data.qpos.copy(), reward, done, {}
def render(self):
"""渲染"""
with viewer.launch_passive(self.rope.model, self.rope.data) as v:
v.sync()
MuJoCo 布料仿真
布料建模
class ClothSimulator:
"""布料仿真"""
def __init__(self, width: int = 10, height: int = 10, cloth_size: float = 0.5):
self.width = width
self.height = height
self.size = cloth_size
self.cell_size = cloth_size / width
self.model = self._build_model(width, height, cloth_size)
self.data = mujoco.MjData(self.model)
def _build_model(self, w: int, h: int, size: float) -> mujoco.Model:
"""构建布料 MuJoCo 模型"""
cell = size / w
h = size / h
n = (w + 1) * (h + 1)
points = []
faces = []
for i in range(h + 1):
for j in range(w + 1):
points.append([j * cell, i * h, 2.0])
for i in range(h):
for j in range(w):
v0 = i * (w + 1) + j
v1 = v0 +
v2 = v0 + w +
v3 = v2 +
faces.extend([v0, v1, v2, v1, v3, v2])
geom_ids = ((n))
xml =
mujoco.from_xml_string(xml)
:
():
.cloth = ClothSimulator(width=, height=, cloth_size=)
.grasper_pos = np.array([, , ])
.grasp_closed =
() -> :
delta = action[:] *
grasp = action[]
.grasper_pos += delta
grasp > .grasp_closed:
._execute_grasp()
mujoco.mj_step(.cloth.model, .cloth.data)
cloth_center = .cloth.data.geom_xpos[]
reward = -np.linalg.norm(.grasper_pos[:] - cloth_center[:])
done =
._get_obs(), reward, done, {}
():
closest_vert = ._find_closest_vert(.grasper_pos)
.grasp_closed =
() -> :
() -> np.ndarray:
np.concatenate([
.grasper_pos,
.cloth.data.qpos.copy(),
])
Isaac Gym 软体环境
Isaac Gym 配置
import isaacgym
import isaacgym.gymapi as gym_api
import isaacgym.gymutil as gymutil
class IsaacGymSoftBody:
"""Isaac Gym 软体环境"""
def __init__(self, num_envs: int = 256):
self.gym = gym_api.acquire_gym()
self.num_envs = num_envs
sim_params = gym_api.SimParams()
sim_params.dt = 1.0 / 60.0
sim_params.substeps = 2
sim_params.up_axis = gym_api.UP_AXIS_Z
sim_params.gravity = gym_api.Vec3(0.0, 0.0, -9.81)
self.sim = self.gym.create_sim(
compute_device=0,
graphics_device=0,
type=gym_api.SIM_PHY_SIM,
params=sim_params
)
self.envs = []
self.actors = []
for i in range(num_envs):
env = self.gym.create_env(
self.sim,
lower=gym_api.Vec3(-0.5, -0.5, 0),
upper=gym_api.Vec3(0.5, 0.5, 1.0)
)
self.envs.append(env)
._create_soft_body(env, i)
():
actor = .gym.create_actor(
env,
._get_soft_body_asset(),
gym_api.Transform(),
.(idx),
idx
)
props = .gym.get_actor_shape_properties(env, actor)
props[].compliance =
props[].friction =
.gym.set_actor_shape_properties(env, actor, props)
.actors.append(actor)
():
():
env .envs:
.gym.reset_actor_states(env)
():
i, env (.envs):
.gym.set_actor_dof_actuation(env, .actors[i], actions[i])
.gym.simulate(.sim)
.gym.fetch_results(.sim, )
Sim2Real 迁移
Domain Randomization
import numpy as np
class DomainRandomizer:
"""Sim2Real 域随机化"""
def __init__(self):
self.param_ranges = {
'gravity': (-10.0, -9.81),
'friction': (0.3, 0.8),
'softness': (0.8, 1.2),
'light_intensity': (0.7, 1.3),
'camera_noise': (0.0, 0.05),
'Kp': (0.8, 1.2),
'Kd': (0.8, 1.2),
}
def randomize(self) -> dict:
"""随机采样参数"""
params = {}
for name, (low, high) in self.param_ranges.items():
params[name] = np.random.uniform(low, high)
return params
():
params:
sim.params.gravity.z = params[]
params:
geom sim.model.geom_friction:
geom[:] = params[]
:
():
.sim = sim_model
.real = real_model
.error_history = []
.max_history =
() -> np.ndarray:
error = real_state - sim_state
.error_history.append(np.linalg.norm(error))
(.error_history) > .max_history:
.error_history.pop()
recent_error = np.mean(.error_history[-:])
recent_error > :
K_adapted = K *
:
K_adapted = K
K_adapted
故障排查
| 问题 | 原因 | 解决方案 |
|---|
| 绳索断裂/不稳定 | 子步长太大 | 减小 timestep,增加 iterations |
| 布料穿透自身 | 碰撞参数不当 | 减小 geom size,增加 solimp |
| 仿真太慢 | 粒子数太多 | 减少段数/顶点数 |
| 抓取滑落 | 摩擦力不足 | 增加 friction,或改用夹爪 |
| Sim2Real 迁移失败 | sim 太理想化 | 添加 domain randomization |
调试命令
python -m mujoco.viewer --model=rope.xml
python -m isaacgym.gymutil --help
python export_sim_data.py --output=cloth_trajectory.npz
相关技能
simulator/mujoco/mujoco-modeling — MuJoCo 模型创建
manipulator/grasp-planning — 抓取规划
manipulator/impedance-control — 柔顺控制
simulator/isaaclab/rl-training — RL 训练
robotics-learning/transfer-learning — 迁移学习