Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_rnea_pick_place.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""RNEA computed-torque pick-place example using PyKDL directly.
3
4Ported from src/examples/ex_rnea_pick_place.cpp. Full computed-torque control
5via KDL ChainIdSolver_RNE. An Env on_reset hook re-homes the arm, re-poses the
6cube, and opens the gripper so the simulate-UI reset replays the task.
7"""
8
9from __future__ import annotations
10
11import argparse
12
13import PyKDL as kdl
14import mj_kdl_wrapper as mjk
15
16HOME = [0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708]
17KP = [100.0, 200.0, 100.0, 200.0, 100.0, 200.0, 100.0]
18KD = [20.0, 28.0, 20.0, 28.0, 20.0, 28.0, 20.0]
19SURFACE_Z = 0.70
20CUBE_START = [0.40, 0.0, SURFACE_Z + 0.02]
21
22
23class ResetRequested(Exception):
24 """Raised when the simulate UI reset is detected, to restart the sequence."""
25
26
27def build_env() -> tuple[mjk.Env, mjk.Robot]:
28 table = mjk.SceneObject()
29 table.name = "table"
30 table.mjcf_path = mjk.menagerie.asset_path("table.xml", env_var="MJ_KDL_TABLE")
31 table.pos = [0.0, 0.0, SURFACE_Z]
32 table.fixed = True
33 cube = mjk.SceneObject()
34 cube.name = "cube"
35 cube.shape = mjk.Shape.BOX
36 cube.size = [0.02, 0.02, 0.02]
37 cube.pos = CUBE_START[:]
38 cube.rgba = [0.1, 0.35, 1.0, 1.0]
39 cube.mass = 0.1
40 cube.friction = [1.0, 0.005, 0.0001]
41
42 attach = mjk.AttachmentSpec()
43 attach.mjcf_path = mjk.menagerie.asset_path("robotiq_2f85/2f85.xml", env_var="MJ_KDL_GRIPPER")
44 attach.attach_to = mjk.AttachTarget(mjk.AttachKind.Site, "pinch_site")
45 attach.prefix = "g_"
46 spec = mjk.SceneSpec()
47 spec.timestep = 0.002
48 spec.add_floor = True
49 spec.add_skybox = True
50 spec.objects = [table, cube]
51 robot_spec = mjk.RobotSpec()
52 robot_spec.path = mjk.menagerie.model_path("kinova_gen3", env_var="MJ_KDL_MODEL")
53 robot_spec.pos = [0.0, 0.0, SURFACE_Z]
54 robot_spec.attachments = [attach]
55 spec.robots = [robot_spec]
56 env = mjk.Env.build(spec)
57 tool = mjk.ToolFrameSpec()
58 tool.tool_body = "g_base"
59 tool.tcp_site = "g_pinch"
60 robot = env.create_robot("base_link", "bracelet_link", tool=tool)
61 return env, robot
62
63
64def jnt(values: list[float]) -> kdl.JntArray:
65 q = kdl.JntArray(len(values))
66 for i, v in enumerate(values):
67 q[i] = v
68 return q
69
70
71def as_list(q: kdl.JntArray) -> list[float]:
72 return [q[i] for i in range(q.rows())]
73
74
75def solve_position_ik(chain: kdl.Chain, seed_values: list[float], target: kdl.Vector) -> list[float]:
76 fk = kdl.ChainFkSolverPos_recursive(chain)
77 ik = kdl.ChainIkSolverVel_wdls(chain)
78 ik.setLambda(0.05)
79 q = jnt(seed_values)
80 dq = kdl.JntArray(q.rows())
81 for _ in range(700):
82 current = kdl.Frame()
83 fk.JntToCart(q, current)
84 dx = kdl.diff(current, kdl.Frame(current.M, target))
85 dx.rot = kdl.Vector.Zero()
86 if dx.vel.Norm() < 0.003:
87 return as_list(q)
88 if dx.vel.Norm() > 0.05:
89 dx.vel = dx.vel * (0.05 / dx.vel.Norm())
90 if ik.CartToJnt(q, dx, dq) < 0:
91 raise RuntimeError("PyKDL IK velocity step failed")
92 for i in range(q.rows()):
93 q[i] += dq[i]
94 raise RuntimeError("PyKDL IK did not converge")
95
96
97def waypoints(chain: kdl.Chain) -> dict[str, list[float]]:
98 seed = HOME[:]
99
100 def solve(pos):
101 nonlocal seed
102 seed = solve_position_ik(chain, seed, kdl.Vector(*pos))
103 return seed[:]
104
105 return {
106 "home": HOME[:],
107 "pick_above": solve([0.40, 0.0, 0.24]),
108 "pick": solve([0.40, 0.0, 0.04]),
109 "lift": solve([0.40, 0.0, 0.34]),
110 "place_above": solve([0.40, 0.24, 0.24]),
111 "place": solve([0.40, 0.24, 0.04]),
112 }
113
114
115def rnea_controller(robot, solver, chain, target: list[float]) -> None:
116 robot.update()
117 q = jnt(robot.jnt_pos_msr)
118 qdot = jnt(robot.jnt_vel_msr)
119 qddot = kdl.JntArray(robot.n_joints)
120 tau = kdl.JntArray(robot.n_joints)
121 for i in range(robot.n_joints):
122 qddot[i] = KP[i] * (target[i] - q[i]) - KD[i] * qdot[i]
123 wrenches = [kdl.Wrench.Zero() for _ in range(chain.getNrOfSegments())]
124 if solver.CartToJnt(q, qdot, qddot, wrenches, tau) < 0:
125 raise RuntimeError("PyKDL RNEA failed")
126 robot.jnt_trq_cmd = as_list(tau)
127
128
129def step_once(env, robot, viewer, state) -> bool:
130 ok = viewer.step() if viewer is not None else robot.step()
131 if not ok:
132 return False
133 if viewer is not None and env.time() < state["prev"] - 1e-6:
134 env.reset()
135 state["prev"] = env.time()
136 raise ResetRequested()
137 state["prev"] = env.time()
138 return True
139
140
141def run_phase(env, robot, solver, chain, phase, viewer, state) -> bool:
142 print(f"State: {phase['name']}")
143 start = robot.jnt_pos_msr[:]
144 t0 = env.time()
145 while env.time() - t0 < phase["duration"]:
146 a = max(0.0, min(1.0, (env.time() - t0) / phase["duration"]))
147 target = [x + a * (y - x) for x, y in zip(start, phase["target"])]
148 rnea_controller(robot, solver, chain, target)
149 if env.has_actuator("g_fingers_actuator"):
150 env.set_actuator_ctrl("g_fingers_actuator", phase["gripper"])
151 if viewer is not None and not viewer.is_running():
152 return False
153 if not step_once(env, robot, viewer, state):
154 return False
155 return True
156
157
158def main() -> int:
159 parser = argparse.ArgumentParser()
160 parser.add_argument("--gui", action="store_true")
161 args = parser.parse_args()
162
163 env, robot = build_env()
164 try:
165 chain = robot.kdl_chain()
166 solver = kdl.ChainIdSolver_RNE(chain, kdl.Vector(0.0, 0.0, -9.81))
167 robot.ctrl_mode = mjk.CtrlMode.TORQUE
168
169 def on_reset(ctx):
170 robot.set_joint_pos(HOME, call_forward=False)
171 env.set_body_pose("cube", CUBE_START)
172 if env.has_actuator("g_fingers_actuator"):
173 env.set_actuator_ctrl("g_fingers_actuator", 0.0)
174
175 env.on_reset = on_reset
176 env.reset()
177
178 q = waypoints(chain)
179 phases = [
180 {"name": "HOME", "target": q["home"], "duration": 0.8, "gripper": 0.0},
181 {"name": "PICK_ABOVE", "target": q["pick_above"], "duration": 2.0, "gripper": 0.0},
182 {"name": "PICK", "target": q["pick"], "duration": 2.0, "gripper": 0.0},
183 {"name": "CLOSE", "target": q["pick"], "duration": 1.0, "gripper": 255.0},
184 {"name": "LIFT", "target": q["lift"], "duration": 1.5, "gripper": 255.0},
185 {"name": "PLACE_ABOVE", "target": q["place_above"], "duration": 1.5, "gripper": 255.0},
186 {"name": "PLACE", "target": q["place"], "duration": 2.0, "gripper": 255.0},
187 {"name": "OPEN", "target": q["place"], "duration": 0.8, "gripper": 0.0},
188 {"name": "RETREAT", "target": q["place_above"], "duration": 1.2, "gripper": 0.0},
189 ]
190 state = {"prev": env.time()}
191 if args.gui:
192 viewer = mjk.SimulateViewer.open(robot, "ex_rnea_pick_place.py")
193 try:
194 while viewer.is_running():
195 try:
196 for phase in phases:
197 if not run_phase(env, robot, solver, chain, phase, viewer, state):
198 raise StopIteration
199 break
200 except ResetRequested:
201 continue
202 except StopIteration:
203 break
204 finally:
205 viewer.close()
206 else:
207 for phase in phases:
208 if not run_phase(env, robot, solver, chain, phase, None, state):
209 break
210 cube_frame = env.body_frame("cube")
211 cube_pos = [cube_frame.p.x(), cube_frame.p.y(), cube_frame.p.z()]
212 print(f"cube final position: {[round(x, 3) for x in cube_pos]}")
213 finally:
214 env.close()
215 return 0
216
217
218if __name__ == "__main__":
219 raise SystemExit(main())
tuple[mjk.Env, mjk.Robot] build_env()
kdl.JntArray jnt(list[float] values)
list[float] as_list(kdl.JntArray q)
bool run_phase(env, robot, solver, chain, phase, viewer, state)
bool step_once(env, robot, viewer, state)
dict[str, list[float]] waypoints(kdl.Chain chain)
list[float] solve_position_ik(kdl.Chain chain, list[float] seed_values, kdl.Vector target)
None rnea_controller(robot, solver, chain, list[float] target)