2"""Table pick-place example ported from src/examples/ex_table_pick_place.cpp.
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.
9from __future__
import annotations
13from dataclasses
import dataclass
16import mj_kdl_wrapper
as mjk
18HOME = [0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708]
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]
31 """Raised when the simulate UI reset is detected, to restart the sequence."""
34@dataclass(frozen=
True)
45 attach = mjk.AttachmentSpec()
46 attach.mjcf_path = gripper_path
47 attach.attach_to = mjk.AttachTarget(mjk.AttachKind.Site,
"pinch_site")
53 table = mjk.SceneObject()
55 table.mjcf_path = table_path
56 table.pos = [0.0, 0.0, SURFACE_Z]
62 cube = mjk.SceneObject()
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]
69 cube.condim = mjk.Condim.Torsional
70 cube.friction = [0.8, 0.02, 0.001]
75 spec = mjk.SceneSpec()
78 spec.add_skybox =
True
80 table_object(mjk.menagerie.asset_path(
"table.xml", env_var=
"MJ_KDL_TABLE")),
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")
92 spec.robots = [robot_spec]
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)
102def jnt(values: list[float]) -> kdl.JntArray:
103 q = kdl.JntArray(len(values))
104 for i, value
in enumerate(values):
110 return [q[i]
for i
in range(q.rows())]
113def clamp(value: float, low: float, high: float) -> float:
114 return max(low, min(high, value))
117def clamp_joint(value: float, limit: tuple[float, float]) -> float:
119 if math.isfinite(low)
and math.isfinite(high)
and high > low:
120 return clamp(value, low, high)
126 seed_values: list[float],
128 joint_limits: list[tuple[float, float]],
130 fk = kdl.ChainFkSolverPos_recursive(chain)
131 ik = kdl.ChainIkSolverVel_wdls(chain)
134 dq = kdl.JntArray(q.rows())
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:
144 dx.vel = dx.vel * (0.05 / pos_err)
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()):
151 raise RuntimeError(
"PyKDL IK did not converge")
155 chain = robot.kdl_chain()
156 target_rot = (env.body_frame(
"bracelet_link").Inverse() * env.site_frame(
"g_pinch")).M
159 def solve(x: float, y: float, z: float) -> list[float]:
162 chain, seed, kdl.Frame(target_rot, kdl.Vector(x, y, z)), robot.joint_limits
166 z_grasp = CUBE_HALF_SIZE
167 z_above = z_grasp + 0.20
168 z_lift = z_grasp + 0.30
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),
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)
189 return max(abs(target[i] - robot.jnt_pos_msr[i])
for i
in range(robot.n_joints))
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)]
197 env: mjk.Env, robot: mjk.Robot, viewer: mjk.SimulateViewer |
None, state: dict
199 ok = viewer.step()
if viewer
is not None else robot.step()
202 if viewer
is not None and env.time() < state[
"prev"] - 1e-6:
204 state[
"prev"] = env.time()
206 state[
"prev"] = env.time()
214 viewer: mjk.SimulateViewer |
None,
217 print(f
"State: {phase.name}")
219 start = robot.jnt_pos_msr[:]
222 elapsed = env.time() - t0
223 alpha =
clamp(elapsed / phase.duration, 0.0, 1.0)
if phase.duration > 0.0
else 1.0
225 if env.has_actuator(
"g_fingers_actuator"):
226 env.set_actuator_ctrl(
"g_fingers_actuator", phase.gripper)
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:
233 if viewer
is not None and not viewer.is_running():
235 if not step_once(env, robot, viewer, state):
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()
247 robot.ctrl_mode = mjk.CtrlMode.TORQUE
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)
255 env.on_reset = on_reset
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),
271 state = {
"prev": env.time()}
273 viewer = mjk.SimulateViewer.open(robot,
"ex_table_pick_place.py")
275 while viewer.is_running():
278 if not run_phase(env, robot, phase, viewer, state):
281 except ResetRequested:
283 except StopIteration:
289 if not run_phase(env, robot, phase,
None, state):
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}")
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)