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