16HOME = [0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708]
21KP = [100.0, 200.0, 100.0, 200.0, 100.0, 200.0, 100.0]
22KD = [10.0, 20.0, 10.0, 20.0, 10.0, 20.0, 10.0]
56 table = mjk.SceneObject()
58 table.mjcf_path = str(mjk.ASSETS_DIR /
"table.xml")
59 table.pos = [0.0, 0.0, SURFACE_Z]
61 cube = mjk.SceneObject()
63 cube.shape = mjk.Shape.BOX
64 cube.size = [CUBE_HS, CUBE_HS, CUBE_HS]
65 cube.pos = CUBE_START[:]
66 cube.rgba = [0.1, 0.35, 1.0, 1.0]
68 cube.condim = mjk.Condim.Torsional
69 cube.friction = [0.8, 0.02, 0.001]
70 gripper = mjk.AttachmentSpec()
71 gripper.mjcf_path = str(mjk.ASSETS_DIR /
"robotiq_2f85/2f85.xml")
72 gripper.attach_to = mjk.AttachTarget(mjk.AttachKind.Site,
"pinch_site")
74 robot_spec = mjk.RobotSpec()
75 robot_spec.path = str(mjk.ASSETS_DIR /
"kinova_gen3/gen3.xml")
76 robot_spec.pos = [0.0, 0.0, SURFACE_Z]
77 robot_spec.attachments = [gripper]
79 spec = mjk.SceneSpec()
82 spec.add_skybox =
True
83 spec.objects = [table, cube]
84 spec.robots = [robot_spec]
85 env = mjk.Env.build(spec)
86 tool = mjk.ToolFrameSpec()
87 tool.tool_body =
"g_base_mount"
88 tool.tcp_site =
"g_pinch"
89 return env, env.create_robot(
"base_link",
"bracelet_link", tool=tool)
105def solve_near_seed(chain, limits, seed: list[float], target: kdl.Frame) -> list[float]:
106 """Damped-least-squares IK stepped from seed, so the answer stays on the seed's branch."""
107 fk = kdl.ChainFkSolverPos_recursive(chain)
108 ik = kdl.ChainIkSolverVel_wdls(chain, 1e-5, 150)
111 dq = kdl.JntArray(q.rows())
113 current = kdl.Frame()
114 fk.JntToCart(q, current)
115 dx = kdl.diff(current, target)
116 if dx.vel.Norm() <= 2e-3
and dx.rot.Norm() <= 2e-2:
118 if dx.vel.Norm() > 0.05:
119 dx.vel = dx.vel * (0.05 / dx.vel.Norm())
120 if dx.rot.Norm() > 0.20:
121 dx.rot = dx.rot * (0.20 / dx.rot.Norm())
122 if ik.CartToJnt(q, dx, dq) < 0:
123 raise RuntimeError(
"PyKDL IK velocity step failed")
125 for i
in range(q.rows()):
126 q[i] = min(limits[i][1], max(limits[i][0], q[i] + dq[i]))
128 current = kdl.Frame()
129 fk.JntToCart(q, current)
130 dx = kdl.diff(current, target)
131 if dx.vel.Norm() > 2e-3
or dx.rot.Norm() > 2e-2:
132 raise RuntimeError(
"PyKDL IK did not converge")
133 return [q[i]
for i
in range(q.rows())]
137 chain = robot.kdl_chain()
141 def solve(xy, z: float) -> list[float]:
143 target = kdl.Frame(robot.tip_T_tcp.M, kdl.Vector(xy[0], xy[1], z))
147 pick_above = solve(PICK, CUBE_HS + 0.20)
148 pick = solve(PICK, CUBE_HS)
149 lift = solve(PICK, CUBE_HS + 0.30)
150 place_above = solve(PLACE, CUBE_HS + 0.20)
151 place = solve(PLACE, CUBE_HS)
152 closed = GRIPPER_CLOSED
154 Phase(
"HOME", HOME, 1.0, 2.5, 0.08, 0.0),
155 Phase(
"PICK_ABOVE", pick_above, 5.0, 7.0, 0.08, 0.0),
156 Phase(
"PICK", pick, 5.0, 8.0, 0.03, 0.0),
157 Phase(
"CLOSE", pick, 1.5, 2.5, -1.0, closed),
158 Phase(
"LIFT", lift, 3.0, 5.0, 0.08, closed),
159 Phase(
"PLACE_ABOVE", place_above, 3.0, 5.0, 0.08, closed),
160 Phase(
"PLACE", place, 5.0, 8.0, 0.03, closed),
161 Phase(
"OPEN", place, 1.0, 2.0, -1.0, 0.0),
162 Phase(
"RETREAT", place_above, 2.0, 4.0, 0.08, 0.0),
163 Phase(
"HOLD", place_above, 1.0, 1.0, -1.0, 0.0),
184def measure_push(env, robot, state: dict, q_ref, cube, t_rel: float, force) ->
None:
185 """Deflection against the reference of the same cycle, and what the stiffness predicts."""
186 m, fk, jac_solver = state[
"metrics"], state[
"fk"], state[
"jac"]
187 base = kdl.Vector(0.0, 0.0, SURFACE_Z)
188 tcp, ref = kdl.Frame(), kdl.Frame()
189 fk.JntToCart(
jnt(robot.jnt_pos_msr), tcp)
190 fk.JntToCart(
jnt(q_ref), ref)
191 m[
"cube_slip"] = max(m[
"cube_slip"], (cube - base - tcp.p).Norm())
195 deflection = kdl.dot(lag - m[
"lag0"], PUSH_DIR)
196 if t_rel >= PUSH_OFF + SETTLE
and m[
"residual"] < 0.0:
197 m[
"residual"] = abs(deflection)
199 full = PUSH_ON + PUSH_RAMP <= t_rel <= PUSH_OFF - PUSH_RAMP
200 if not full
or deflection <= m[
"peak"]:
205 jac = kdl.Jacobian(n)
206 jac_solver.JntToJac(
jnt(q_ref), jac)
207 jac_push = kdl.Jacobian(jac)
208 jac_push.changeRefPoint(kdl.Vector(*env.data.body(PUSH_BODY).xipos) - base - ref.p)
211 dx += jac.getColumn(i).vel * (kdl.dot(jac_push.getColumn(i).vel, force) / KP[i])
212 m[
"peak"] = deflection
213 m[
"predicted"] = kdl.dot(dx, PUSH_DIR)
216def run_phase(env, robot, fingers, dyn, phase: Phase, state: dict) -> bool:
217 """One update() per step: it reads the state and applies the previous cycle's command."""
218 print(f
"State: {phase.name}")
219 start = robot.jnt_pos_msr[:]
221 push = env.data.body(PUSH_BODY)
222 pushing = phase.name == PUSH_PHASE
225 elapsed = env.data.time - t0
226 alpha = min(1.0, max(0.0, elapsed / phase.duration))
if phase.duration > 0.0
else 1.0
227 q, qd = robot.jnt_pos_msr, robot.jnt_vel_msr
228 q_ref = [start[i] + alpha * (phase.target[i] - start[i])
for i
in range(len(q))]
230 robot.jnt_trq_cmd = [
231 g[i] + KP[i] * (q_ref[i] - q[i]) - KD[i] * qd[i]
for i
in range(len(q))
233 fingers.ctrl[0] = phase.gripper
234 cube = env.body_frame(
"cube").p
if pushing
else None
235 err = max(abs(t - m)
for t, m
in zip(phase.target, q))
236 done_pose = phase.settle_tol < 0.0
or err <= phase.settle_tol
237 if (elapsed >= phase.duration
and done_pose)
or elapsed >= phase.timeout:
242 state[
"reset"] =
False
245 metrics = state[
"metrics"]
246 metrics[
"elbow_min"] = min(metrics[
"elbow_min"], env.body_frame(
"forearm_link").p.z())
247 force = PUSH_DIR * (PUSH_FORCE *
push_scale(elapsed)
if pushing
else 0.0)
248 push.xfrc_applied[:] = [force.x(), force.y(), force.z(), 0.0, 0.0, 0.0]
250 measure_push(env, robot, state, q_ref, cube, elapsed, force)
254 parser = argparse.ArgumentParser()
255 parser.add_argument(
"--gui", action=
"store_true")
256 args = parser.parse_args()
260 robot.set_control_mode(mjk.CtrlMode.TORQUE)
261 fingers = env.data.actuator(
"g_fingers_actuator")
262 chain = robot.kdl_chain()
263 dyn = kdl.ChainDynParam(chain, kdl.Vector(0.0, 0.0, env.spec.gravity_z))
268 "fk": kdl.ChainFkSolverPos_recursive(chain),
269 "jac": kdl.ChainJntToJacSolver(chain),
273 robot.set_joint_pos(HOME)
274 env.set_body_pose(
"cube", CUBE_START)
275 fingers.ctrl[0] = 0.0
276 robot.jnt_trq_cmd =
gravity(dyn, HOME)
278 state[
"reset"] =
True
280 env.on_reset = on_reset
282 state[
"reset"] =
False
284 env.open_viewer(
"ex_table_pick_place.py")
289 completed = all(
run_phase(env, robot, fingers, dyn, p, state)
for p
in phases)
291 except ResetRequested:
295 cube = env.body_frame(
"cube").p
296 place_err = math.hypot(cube.x() - PLACE[0], cube.y() - PLACE[1])
297 on_table = abs(cube.z() - (SURFACE_Z + CUBE_HS)) < 0.002
299 ratio = m[
"peak"] / m[
"predicted"]
if m[
"predicted"] > 0.0
else 0.0
300 elbow_h = m[
"elbow_min"] - SURFACE_Z
302 f
"cube final position: [{cube.x():.4f}, {cube.y():.4f}, {cube.z():.4f}] "
303 f
"place error {place_err * 1000:.4f} mm (limit {MAX_PLACE_ERR * 1000} mm)"
304 + (
"" if on_table
else ", not on the table")
307 f
"push deflection: {m['peak'] * 1000:.4f} mm (at least {MIN_DEFLECTION * 1000} mm), "
308 f
"stiffness predicts {m['predicted'] * 1000:.4f} mm, ratio {ratio:.4f} "
309 f
"(limits {DEFLECTION_RATIO[0]}..{DEFLECTION_RATIO[1]})"
312 f
"deflection {SETTLE} s after the push: {m['residual'] * 1000:.4f} mm "
313 f
"(limit {MAX_RESIDUAL * 1000} mm)"
316 f
"cube from the TCP while pushed: {m['cube_slip'] * 1000:.4f} mm "
317 f
"(limit {MAX_CUBE_SLIP * 1000} mm)"
320 f
"lowest elbow above the table: {elbow_h * 1000:.4f} mm "
321 f
"(limit {MIN_ELBOW_HEIGHT * 1000} mm)"
330 and place_err <= MAX_PLACE_ERR
331 and m[
"peak"] >= MIN_DEFLECTION
332 and DEFLECTION_RATIO[0] <= ratio <= DEFLECTION_RATIO[1]
333 and 0.0 <= m[
"residual"] <= MAX_RESIDUAL
334 and m[
"cube_slip"] <= MAX_CUBE_SLIP
335 and elbow_h >= MIN_ELBOW_HEIGHT
337 print(f
"{'PASS' if ok else 'FAIL'}: the cube was placed through the push")
338 return 0
if ok
else 1