13HOME = [0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708]
24KP = [120.0, 220.0, 120.0, 220.0, 110.0, 190.0, 90.0]
25KD = [12.0, 22.0, 12.0, 22.0, 11.0, 18.0, 9.0]
76 ball = mjk.SceneObject()
77 ball.name = f
"grain_{index:02d}"
78 ball.shape = mjk.Shape.SPHERE
79 ball.size = [BALL_RADIUS, 0.0, 0.0]
80 ball.pos = [0.0, 0.0, TABLE_Z + 0.40 + index * 2.0 * BALL_RADIUS]
81 ball.rgba = [1.0, 0.84, 0.30, 1.0]
83 ball.condim = mjk.Condim.Torsional
84 ball.friction = [0.5, 0.02, 0.001]
89 spec = mjk.SceneSpec()
92 spec.add_skybox =
True
94 table_object(mjk.menagerie.asset_path(
"table.xml", env_var=
"MJ_KDL_TABLE")),
96 receiver_object(mjk.menagerie.asset_path(
"mug_table.xml", env_var=
"MJ_KDL_RECEIVER")),
99 robot_spec = mjk.RobotSpec()
100 robot_spec.path = mjk.menagerie.model_path(
"kinova_gen3", env_var=
"MJ_KDL_MODEL")
101 robot_spec.pos = [ROBOT_BACK_X, 0.0, TABLE_Z]
102 robot_spec.attachments = [
104 mjk.menagerie.asset_path(
"robotiq_2f85/2f85.xml", env_var=
"MJ_KDL_GRIPPER")
108 spec.robots = [robot_spec]
110 env = mjk.Env.build(spec)
111 tool = mjk.ToolFrameSpec()
112 tool.tool_body =
"g_base"
113 tool.tcp_site =
"g_pinch"
114 robot = env.create_robot(
"base_link",
"bracelet_link", tool=tool)
141 q_min = kdl.JntArray(robot.n_joints)
142 q_max = kdl.JntArray(robot.n_joints)
143 for i, (low, high)
in enumerate(robot.joint_limits):
144 if math.isfinite(low)
and math.isfinite(high)
and high > low:
145 q_min[i], q_max[i] = low, high
147 q_min[i], q_max[i] = -2.0 * math.pi, 2.0 * math.pi
152 chain = robot.kdl_chain()
154 fk = kdl.ChainFkSolverPos_recursive(chain)
156 ik_vel = kdl.ChainIkSolverVel_pinv(chain)
157 ik_nr = kdl.ChainIkSolverPos_NR_JL(chain, q_min, q_max, fk, ik_vel, 2000, 1e-5)
158 ik_lma = kdl.ChainIkSolverPos_LMA(chain, 1e-5, 2000)
161 home_fk = kdl.Frame()
162 fk.JntToCart(q_home, home_fk)
164 carry_tcp = home_fk.M * kdl.Rotation.RotY(-0.05)
166 world_T_base = kdl.Frame(kdl.Rotation.Identity(), kdl.Vector(ROBOT_BACK_X, 0.0, TABLE_Z))
167 base_T_world = world_T_base.Inverse()
170 robot.set_joint_pos(HOME, call_forward=
False)
171 world_T_outlet = env.site_frame(
"pour_outlet")
172 world_T_tcp = env.site_frame(
"g_pinch")
173 tcp_outlet = world_T_tcp.Inverse() * world_T_outlet.p
175 def outlet_target_to_tcp_target(tcp_rot: kdl.Rotation, outlet_pos: kdl.Vector) -> kdl.Frame:
176 return kdl.Frame(tcp_rot, outlet_pos - tcp_rot * tcp_outlet)
178 def solve(name: str, seed_values: list[float], outlet_pos: kdl.Vector) -> list[float]:
179 target = base_T_world * outlet_target_to_tcp_target(carry_tcp, outlet_pos)
180 seed =
jnt(seed_values)
181 out = kdl.JntArray(n)
182 ok = ik_nr.CartToJnt(seed, target, out) >= 0
184 ok = ik_lma.CartToJnt(seed, target, out) >= 0
186 raise RuntimeError(f
"IK failed for {name}")
188 fk.JntToCart(out, fk_out)
189 if (target.p - fk_out.p).Norm() > IK_TOL:
190 raise RuntimeError(f
"IK pose error for {name}")
193 q_pre_pour = solve(
"pre-pour", HOME, kdl.Vector(JUG_X, JUG_Y, TABLE_Z + 0.27))
194 q_pour = solve(
"pour", q_pre_pour, kdl.Vector(JUG_X, JUG_Y, TABLE_Z + 0.20))
195 q_retreat = solve(
"retreat", q_pour, kdl.Vector(RETREAT_X, RETREAT_Y, TABLE_Z + 0.27))
197 q_tilt[-1] =
clamp_joint(q_tilt[-1] + POUR_TILT_RAD, robot.joint_limits[-1])
200 "pre_pour": q_pre_pour,
203 "retreat": q_retreat,
225 robot.set_joint_pos(HOME, call_forward=
True)
226 center = env.site_frame(
"pour_center")
227 spacing = 2.0 * BALL_RADIUS
228 for i
in range(NUM_BALLS):
231 ix = float(slot % 3) - 1.0
232 iy = float(slot // 3) - 1.0
233 local = kdl.Vector(ix * spacing, iy * spacing, -0.026 + layer * spacing)
234 world = center * local
235 env.set_body_pose(f
"grain_{i:02d}", [world.x(), world.y(), world.z()])
240 centroid = [0.0, 0.0, 0.0]
241 for i
in range(NUM_BALLS):
242 frame = env.body_frame(f
"grain_{i:02d}")
243 pos = [frame.p.x(), frame.p.y(), frame.p.z()]
244 centroid = [a + b
for a, b
in zip(centroid, pos)]
246 abs(pos[0] - JUG_X) < 0.040
247 and abs(pos[1] - JUG_Y) < 0.040
248 and TABLE_Z + 0.004 < pos[2] < TABLE_Z + 0.13
251 return count, [value / NUM_BALLS
for value
in centroid]
264 viewer: mjk.SimulateViewer |
None,
265 recorder: mjk.VideoRecorder |
None,
267 step_counter: list[int],
270 print(f
"State: {phase.name}")
272 start = robot.jnt_pos_msr[:]
275 elapsed = env.time() - t0
276 alpha =
clamp(elapsed / phase.duration, 0.0, 1.0)
if phase.duration > 0.0
else 1.0
278 if env.has_actuator(
"g_fingers_actuator"):
279 env.set_actuator_ctrl(
"g_fingers_actuator", phase.gripper)
281 done_time = elapsed >= phase.duration
282 done_pose = phase.settle_tol < 0.0
or max_abs_joint_err(robot, phase.target) <= phase.settle_tol
283 done_timeout = phase.timeout > 0.0
and elapsed >= phase.timeout
284 if (done_time
and done_pose)
or done_timeout:
286 if viewer
is not None and not viewer.is_running():
290 if viewer
is not None and env.time() < state[
"prev"] - 1e-6:
292 state[
"prev"] = env.time()
294 state[
"prev"] = env.time()
296 if recorder
is not None and step_counter[0] % record_every == 0:
297 recorder.record_frame()
301 parser = argparse.ArgumentParser()
302 parser.add_argument(
"--gui", action=
"store_true")
303 parser.add_argument(
"--record", nargs=
"?", const=
"table_pour.mp4")
304 parser.add_argument(
"--headless", action=
"store_true", help=argparse.SUPPRESS)
305 args = parser.parse_args()
310 robot.ctrl_mode = mjk.CtrlMode.TORQUE
314 if env.has_actuator(
"g_fingers_actuator"):
315 env.set_actuator_ctrl(
"g_fingers_actuator", 255.0)
317 env.on_reset = on_reset
322 Phase(
"HOME", waypoints[
"home"], 0.8, 2.0, 0.08, 255.0),
323 Phase(
"PRE_POUR", waypoints[
"pre_pour"], 2.0, 4.0, 0.08, 255.0),
324 Phase(
"POUR", waypoints[
"pour"], 1.8, 4.0, 0.07, 255.0),
325 Phase(
"TILT", waypoints[
"tilt"], 3.0, 5.0, 0.07, 255.0),
326 Phase(
"POUR_HOLD", waypoints[
"tilt"], 2.5
if not args.gui
else 10.0, 0.0, -1.0, 255.0),
327 Phase(
"RETREAT", waypoints[
"retreat"], 1.6, 3.0, 0.08, 255.0),
328 Phase(
"HOLD", waypoints[
"retreat"], 1.0
if not args.gui
else 10.0, 0.0, -1.0, 255.0),
332 record_every = max(1, int(1.0 / (fps * env.timestep())))
334 recorder = mjk.VideoRecorder.open_preset(
335 env, args.record, mjk.VideoResolution.R1080p, fps
338 state = {
"prev": env.time()}
340 viewer = mjk.SimulateViewer.open(robot,
"ex_table_pour.py")
342 while viewer.is_running():
346 env, robot, phase, viewer, recorder, record_every, step_counter, state
350 except ResetRequested:
352 except StopIteration:
359 env, robot, phase,
None, recorder, record_every, step_counter, state
363 print(f
"balls in transparent receiver: {in_receiver}/{NUM_BALLS}")
364 print(f
"grain centroid: {[round(v, 3) for v in centroid]} receiver center={[JUG_X, JUG_Y]}")
365 if recorder
is not None:
366 print(f
"recorded: {args.record}")
368 if recorder
is not None:
bool run_phase(mjk.Env env, mjk.Robot robot, Phase phase, mjk.SimulateViewer|None viewer, mjk.VideoRecorder|None recorder, int record_every, list[int] step_counter, dict state)