Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_pick.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Scripted floor pick example ported from src/examples/ex_pick.cpp.
3
4Joint-impedance control (tau = Kp*e - Kd*qdot + g_kdl) drives scripted
5waypoints to grasp and lift a cube. An Env on_reset hook re-homes the arm,
6re-poses the cube, and opens the gripper, so the simulate-UI reset button
7replays the whole pick.
8"""
9
10from __future__ import annotations
11
12import argparse
13import math
14from dataclasses import dataclass
15
16import PyKDL as kdl
17import mj_kdl_wrapper as mjk
18
19HOME = [0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708]
20CUBE_X = 0.40
21CUBE_Y = 0.00
22CUBE_HALF_SIZE = 0.02
23CUBE_Z = CUBE_HALF_SIZE
24KP = [100.0, 200.0, 100.0, 200.0, 100.0, 200.0, 100.0]
25KD = [10.0, 20.0, 10.0, 20.0, 10.0, 20.0, 10.0]
26
27
28class ResetRequested(Exception):
29 """Raised when the simulate UI reset is detected, to restart the sequence."""
30
31
32@dataclass(frozen=True)
33class Phase:
34 name: str
35 target: list[float]
36 duration: float
37 timeout: float
38 settle_tol: float
39 gripper: float
40
41
42def gripper_attachment(gripper_path: str) -> mjk.AttachmentSpec:
43 attach = mjk.AttachmentSpec()
44 attach.mjcf_path = gripper_path
45 attach.attach_to = mjk.AttachTarget(mjk.AttachKind.Site, "pinch_site")
46 attach.prefix = "g_"
47 return attach
48
49
50def cube_object() -> mjk.SceneObject:
51 cube = mjk.SceneObject()
52 cube.name = "cube"
53 cube.shape = mjk.Shape.BOX
54 cube.size = [CUBE_HALF_SIZE, CUBE_HALF_SIZE, CUBE_HALF_SIZE]
55 cube.pos = [CUBE_X, CUBE_Y, CUBE_Z]
56 cube.rgba = [1.0, 0.5, 0.0, 1.0]
57 cube.mass = 0.1
58 cube.condim = mjk.Condim.Torsional
59 cube.friction = [0.8, 0.02, 0.001]
60 return cube
61
62
63def build_env() -> tuple[mjk.Env, mjk.Robot]:
64 spec = mjk.SceneSpec()
65 spec.timestep = 0.002
66 spec.add_floor = True
67 spec.add_skybox = True
68 spec.objects = [cube_object()]
69
70 robot_spec = mjk.RobotSpec()
71 robot_spec.path = mjk.menagerie.model_path("kinova_gen3", env_var="MJ_KDL_MODEL")
72 robot_spec.attachments = [
74 mjk.menagerie.asset_path("robotiq_2f85/2f85.xml", env_var="MJ_KDL_GRIPPER")
75 )
76 ]
77 spec.robots = [robot_spec]
78
79 env = mjk.Env.build(spec)
80 tool = mjk.ToolFrameSpec()
81 tool.tool_body = "g_base"
82 tool.tcp_site = "g_pinch"
83 robot = env.create_robot("base_link", "bracelet_link", tool=tool)
84 return env, robot
85
86
87def jnt(values: list[float]) -> kdl.JntArray:
88 q = kdl.JntArray(len(values))
89 for i, value in enumerate(values):
90 q[i] = value
91 return q
92
93
94def as_list(q: kdl.JntArray) -> list[float]:
95 return [q[i] for i in range(q.rows())]
96
97
98def clamp(value: float, low: float, high: float) -> float:
99 return max(low, min(high, value))
100
101
102def clamp_joint(value: float, limit: tuple[float, float]) -> float:
103 low, high = limit
104 if math.isfinite(low) and math.isfinite(high) and high > low:
105 return clamp(value, low, high)
106 return value
107
108
110 chain: kdl.Chain,
111 seed_values: list[float],
112 target: kdl.Frame,
113 joint_limits: list[tuple[float, float]],
114) -> list[float]:
115 fk = kdl.ChainFkSolverPos_recursive(chain)
116 ik = kdl.ChainIkSolverVel_wdls(chain)
117 ik.setLambda(0.05)
118 q = jnt(seed_values)
119 dq = kdl.JntArray(q.rows())
120 for _ in range(700):
121 current = kdl.Frame()
122 fk.JntToCart(q, current)
123 dx = kdl.diff(current, target)
124 pos_err = dx.vel.Norm()
125 rot_err = dx.rot.Norm()
126 if pos_err <= 0.003 and rot_err <= 0.03:
127 return as_list(q)
128 if pos_err > 0.05:
129 dx.vel = dx.vel * (0.05 / pos_err)
130 if rot_err > 0.20:
131 dx.rot = dx.rot * (0.20 / rot_err)
132 if ik.CartToJnt(q, dx, dq) < 0:
133 raise RuntimeError("PyKDL IK velocity step failed")
134 for i in range(q.rows()):
135 q[i] = clamp_joint(q[i] + dq[i], joint_limits[i])
136 raise RuntimeError("PyKDL IK did not converge")
137
138
139def build_waypoints(env: mjk.Env, robot: mjk.Robot) -> dict[str, list[float]]:
140 chain = robot.kdl_chain()
141 target_rot = (env.body_frame("bracelet_link").Inverse() * env.site_frame("g_pinch")).M
142 seed = HOME[:]
143
144 def solve(x: float, y: float, z: float) -> list[float]:
145 nonlocal seed
146 seed = solve_position_ik(
147 chain, seed, kdl.Frame(target_rot, kdl.Vector(x, y, z)), robot.joint_limits
148 )
149 return seed[:]
150
151 return {
152 "home": HOME[:],
153 "pregrasp": solve(CUBE_X, CUBE_Y, CUBE_Z + 0.20),
154 "grasp": solve(CUBE_X, CUBE_Y, CUBE_Z),
155 "lift": solve(CUBE_X, CUBE_Y, CUBE_Z + 0.30),
156 }
157
158
159def apply_pd_gravity(robot: mjk.Robot, target: list[float]) -> None:
160 robot.update()
161 gravity = robot.gravity_torques(-9.81)
162 robot.jnt_trq_cmd = [
163 KP[i] * (target[i] - robot.jnt_pos_msr[i]) - KD[i] * robot.jnt_vel_msr[i] + gravity[i]
164 for i in range(robot.n_joints)
165 ]
166
167
168def max_abs_joint_err(robot: mjk.Robot, target: list[float]) -> float:
169 return max(abs(target[i] - robot.jnt_pos_msr[i]) for i in range(robot.n_joints))
170
171
172def lerp(start: list[float], target: list[float], alpha: float) -> list[float]:
173 return [a + alpha * (b - a) for a, b in zip(start, target)]
174
175
177 env: mjk.Env, robot: mjk.Robot, viewer: mjk.SimulateViewer | None, state: dict
178) -> bool:
179 ok = viewer.step() if viewer is not None else robot.step()
180 if not ok:
181 return False
182 if viewer is not None and env.time() < state["prev"] - 1e-6: # UI reset pressed
183 env.reset()
184 state["prev"] = env.time()
185 raise ResetRequested()
186 state["prev"] = env.time()
187 return True
188
189
191 env: mjk.Env,
192 robot: mjk.Robot,
193 phase: Phase,
194 viewer: mjk.SimulateViewer | None,
195 state: dict,
196) -> bool:
197 print(f"State: {phase.name}")
198 robot.update()
199 start = robot.jnt_pos_msr[:]
200 t0 = env.time()
201 while True:
202 elapsed = env.time() - t0
203 alpha = clamp(elapsed / phase.duration, 0.0, 1.0) if phase.duration > 0.0 else 1.0
204 apply_pd_gravity(robot, lerp(start, phase.target, alpha))
205 if env.has_actuator("g_fingers_actuator"):
206 env.set_actuator_ctrl("g_fingers_actuator", phase.gripper)
207
208 done_time = elapsed >= phase.duration
209 done_pose = phase.settle_tol < 0.0 or max_abs_joint_err(robot, phase.target) <= phase.settle_tol
210 done_timeout = phase.timeout > 0.0 and elapsed >= phase.timeout
211 if (done_time and done_pose) or done_timeout:
212 return True
213 if viewer is not None and not viewer.is_running():
214 return False
215 if not step_once(env, robot, viewer, state):
216 return False
217
218
219def main() -> int:
220 parser = argparse.ArgumentParser()
221 parser.add_argument("--gui", action="store_true")
222 parser.add_argument("--headless", action="store_true", help=argparse.SUPPRESS)
223 args = parser.parse_args()
224
225 env, robot = build_env()
226 try:
227 robot.ctrl_mode = mjk.CtrlMode.TORQUE
228
229 def on_reset(ctx):
230 robot.set_joint_pos(HOME, call_forward=False)
231 env.set_body_pose("cube", [CUBE_X, CUBE_Y, CUBE_Z])
232 if env.has_actuator("g_fingers_actuator"):
233 env.set_actuator_ctrl("g_fingers_actuator", 0.0)
234
235 env.on_reset = on_reset
236 env.reset()
237
238 waypoints = build_waypoints(env, robot)
239 phases = [
240 Phase("HOME", waypoints["home"], 1.0, 2.5, 0.08, 0.0),
241 Phase("PREGRASP", waypoints["pregrasp"], 5.0, 7.0, 0.08, 0.0),
242 Phase("GRASP", waypoints["grasp"], 5.0, 8.0, 0.03, 0.0),
243 Phase("CLOSE", waypoints["grasp"], 1.5, 2.5, -1.0, 255.0),
244 Phase("LIFT", waypoints["lift"], 3.0, 5.0, 0.08, 255.0),
245 Phase("HOLD", waypoints["lift"], 1.0 if not args.gui else 10.0, 0.0, -1.0, 255.0),
246 ]
247 state = {"prev": env.time()}
248 if args.gui:
249 viewer = mjk.SimulateViewer.open(robot, "ex_pick.py")
250 try:
251 while viewer.is_running():
252 try:
253 for phase in phases:
254 if not run_phase(env, robot, phase, viewer, state):
255 raise StopIteration
256 break
257 except ResetRequested:
258 continue
259 except StopIteration:
260 break
261 finally:
262 viewer.close()
263 else:
264 for phase in phases:
265 if not run_phase(env, robot, phase, None, state):
266 break
267 cube = env.body_frame("cube")
268 print(f"final cube height: {cube.p.z():.3f} m")
269 finally:
270 env.close()
271 return 0
272
273
274if __name__ == "__main__":
275 raise SystemExit(main())
None apply_pd_gravity(mjk.Robot robot, list[float] target)
Definition ex_pick.py:159
float clamp(float value, float low, float high)
Definition ex_pick.py:98
kdl.JntArray jnt(list[float] values)
Definition ex_pick.py:87
list[float] as_list(kdl.JntArray q)
Definition ex_pick.py:94
float clamp_joint(float value, tuple[float, float] limit)
Definition ex_pick.py:102
list[float] lerp(list[float] start, list[float] target, float alpha)
Definition ex_pick.py:172
dict[str, list[float]] build_waypoints(mjk.Env env, mjk.Robot robot)
Definition ex_pick.py:139
bool step_once(mjk.Env env, mjk.Robot robot, mjk.SimulateViewer|None viewer, dict state)
Definition ex_pick.py:178
list[float] solve_position_ik(kdl.Chain chain, list[float] seed_values, kdl.Frame target, list[tuple[float, float]] joint_limits)
Definition ex_pick.py:114
float max_abs_joint_err(mjk.Robot robot, list[float] target)
Definition ex_pick.py:168
int main()
Definition ex_pick.py:219
tuple[mjk.Env, mjk.Robot] build_env()
Definition ex_pick.py:63
mjk.SceneObject cube_object()
Definition ex_pick.py:50
mjk.AttachmentSpec gripper_attachment(str gripper_path)
Definition ex_pick.py:42
bool run_phase(mjk.Env env, mjk.Robot robot, Phase phase, mjk.SimulateViewer|None viewer, dict state)
Definition ex_pick.py:196