|
|
import pybullet as p |
|
|
import pybullet_data |
|
|
import numpy as np |
|
|
import matplotlib.pyplot as plt |
|
|
import tempfile |
|
|
import gradio as gr |
|
|
import time |
|
|
|
|
|
p.connect(p.DIRECT) |
|
|
p.setAdditionalSearchPath(pybullet_data.getDataPath()) |
|
|
p.setGravity(0, 0, -9.8) |
|
|
p.loadURDF("plane.urdf") |
|
|
robot = p.loadURDF("franka_panda/panda.urdf", basePosition=[0, 0, 0], useFixedBase=True) |
|
|
cube_id = p.loadURDF("cube_small.urdf", basePosition=[0.6, 0, 0.02]) |
|
|
p.changeVisualShape(cube_id, -1, rgbaColor=[0, 0, 0, 1]) |
|
|
|
|
|
def get_panda_joints(robot): |
|
|
arm, fingers = [], [] |
|
|
for i in range(p.getNumJoints(robot)): |
|
|
info = p.getJointInfo(robot, i) |
|
|
name = info[1].decode() |
|
|
joint_type = info[2] |
|
|
if "finger" in name and joint_type == p.JOINT_PRISMATIC: |
|
|
fingers.append(i) |
|
|
elif joint_type == p.JOINT_REVOLUTE: |
|
|
arm.append(i) |
|
|
return arm, fingers |
|
|
|
|
|
arm_joints, finger_joints = get_panda_joints(robot) |
|
|
|
|
|
debug_labels = [] |
|
|
def add_joint_labels(): |
|
|
global debug_labels |
|
|
for i in debug_labels: |
|
|
p.removeUserDebugItem(i) |
|
|
debug_labels.clear() |
|
|
for idx in arm_joints: |
|
|
link_state = p.getLinkState(robot, idx) |
|
|
pos = link_state[0] |
|
|
lbl = f"J{arm_joints.index(idx)+1}" |
|
|
text_id = p.addUserDebugText(lbl, pos, textColorRGB=[1, 0, 0], textSize=1.2) |
|
|
debug_labels.append(text_id) |
|
|
|
|
|
def render_sim(joint_values, gripper_val, cam_xyz, target_xyz): |
|
|
for idx, tgt in zip(arm_joints, joint_values): |
|
|
p.setJointMotorControl2(robot, idx, p.POSITION_CONTROL, targetPosition=tgt) |
|
|
if len(finger_joints) == 2: |
|
|
for fj in finger_joints: |
|
|
p.setJointMotorControl2(robot, fj, p.POSITION_CONTROL, targetPosition=gripper_val) |
|
|
for _ in range(10): p.stepSimulation() |
|
|
add_joint_labels() |
|
|
width, height = 1280, 1280 |
|
|
view_matrix = p.computeViewMatrix(cam_xyz, target_xyz, [0, 0, 1]) |
|
|
proj_matrix = p.computeProjectionMatrixFOV(60, width / height, 0.1, 3.1) |
|
|
_, _, img, _, _ = p.getCameraImage(width, height, view_matrix, proj_matrix) |
|
|
rgb = np.reshape(img, (height, width, 4))[:, :, :3] |
|
|
fig, ax = plt.subplots(figsize=(5, 5)) |
|
|
ax.imshow(rgb.astype(np.uint8)) |
|
|
ax.axis("off") |
|
|
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") |
|
|
plt.savefig(tmp.name, bbox_inches='tight', dpi=200) |
|
|
plt.close() |
|
|
joint_text = "Joint Angles:\n" + "\n".join([f"J{i+1} = {v:.2f} rad" for i, v in enumerate(joint_values)]) |
|
|
joint_text += f"\nGripper = {gripper_val:.3f} m" |
|
|
return tmp.name, joint_text |
|
|
|
|
|
def move_to_input_angles(joint_str, cam_xyz, target_xyz): |
|
|
try: |
|
|
target_angles = [float(x.strip()) for x in joint_str.split(",")] |
|
|
if len(target_angles) != 7: |
|
|
return None, "β Please enter exactly 7 joint angles." |
|
|
current = [p.getJointState(robot, idx)[0] for idx in arm_joints] |
|
|
steps = 100 |
|
|
for i in range(steps): |
|
|
blend = [(1 - i/steps) * c + (i/steps) * t for c, t in zip(current, target_angles)] |
|
|
for idx, val in zip(arm_joints, blend): |
|
|
p.setJointMotorControl2(robot, idx, p.POSITION_CONTROL, targetPosition=val) |
|
|
p.stepSimulation() |
|
|
time.sleep(0.01) |
|
|
current_grip = p.getJointState(robot, finger_joints[0])[0] |
|
|
return render_sim(target_angles, current_grip, cam_xyz, target_xyz) |
|
|
except Exception as e: |
|
|
return None, f"Error: {str(e)}" |
|
|
|
|
|
def pick_and_place(position_str, approach_str, place_str, cam_xyz, target_xyz): |
|
|
try: |
|
|
position_angles = [float(x.strip()) for x in position_str.split(",")] |
|
|
approach_angles = [float(x.strip()) for x in approach_str.split(",")] |
|
|
place_angles = [float(x.strip()) for x in place_str.split(",")] |
|
|
|
|
|
if len(position_angles) != 7 or len(approach_angles) != 7 or len(place_angles) != 7: |
|
|
return None, "β All inputs must have 7 joint angles." |
|
|
|
|
|
|
|
|
move_to_input_angles(approach_str, cam_xyz, target_xyz) |
|
|
|
|
|
|
|
|
steps = 50 |
|
|
for i in range(steps): |
|
|
interpolated = [(1 - i/steps) * approach_angles[j] + (i/steps) * position_angles[j] for j in range(7)] |
|
|
for idx, val in zip(arm_joints, interpolated): |
|
|
p.setJointMotorControl2(robot, idx, p.POSITION_CONTROL, targetPosition=val) |
|
|
p.stepSimulation() |
|
|
time.sleep(0.01) |
|
|
|
|
|
|
|
|
for _ in range(30): |
|
|
for fj in finger_joints: |
|
|
p.setJointMotorControl2(robot, fj, p.POSITION_CONTROL, targetPosition=0.0) |
|
|
p.stepSimulation() |
|
|
time.sleep(0.01) |
|
|
|
|
|
|
|
|
lifted_angles = [a + 0.1 for a in position_angles] |
|
|
for i in range(steps): |
|
|
lifted_pose = [(1 - i/steps) * position_angles[j] + (i/steps) * lifted_angles[j] for j in range(7)] |
|
|
for idx, val in zip(arm_joints, lifted_pose): |
|
|
p.setJointMotorControl2(robot, idx, p.POSITION_CONTROL, targetPosition=val) |
|
|
p.stepSimulation() |
|
|
time.sleep(0.01) |
|
|
|
|
|
|
|
|
move_to_input_angles(place_str, cam_xyz, target_xyz) |
|
|
|
|
|
|
|
|
for _ in range(30): |
|
|
for fj in finger_joints: |
|
|
p.setJointMotorControl2(robot, fj, p.POSITION_CONTROL, targetPosition=0.04) |
|
|
p.stepSimulation() |
|
|
time.sleep(0.01) |
|
|
|
|
|
return render_sim(place_angles, 0.04, cam_xyz, target_xyz) |
|
|
except Exception as e: |
|
|
return None, f"Error: {str(e)}" |
|
|
|
|
|
|
|
|
|
|
|
def copy_current_joint_angles(*vals): |
|
|
return ", ".join([f"{v:.4f}" for v in vals]) |
|
|
|
|
|
with gr.Blocks(title="Franka Arm with 3D Camera Control") as demo: |
|
|
gr.Markdown("## π€ Franka Robot with Camera + Joint Control") |
|
|
|
|
|
|
|
|
joint_sliders = [] |
|
|
with gr.Row(): |
|
|
for i in range(4): |
|
|
joint_sliders.append(gr.Slider(-3.14, 3.14, value=0, label=f"Joint {i+1}")) |
|
|
with gr.Row(): |
|
|
for i in range(4, 7): |
|
|
joint_sliders.append(gr.Slider(-3.14, 3.14, value=0, label=f"Joint {i+1}")) |
|
|
gripper = gr.Slider(0.0, 0.04, value=0.02, step=0.001, label="Gripper") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("**Camera Position**", elem_id="camera-position-label") |
|
|
cam_x = gr.Slider(-3, 3, value=1.5, label="X") |
|
|
cam_y = gr.Slider(-3, 3, value=0.0, label="Y") |
|
|
cam_z = gr.Slider(-1, 3, value=1.0, label="Z") |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("**Target Point**", elem_id="target-point-label") |
|
|
tgt_x = gr.Slider(-1, 1, value=0.0, label="X") |
|
|
tgt_y = gr.Slider(-1, 1, value=0.0, label="Y") |
|
|
tgt_z = gr.Slider(0, 2, value=0.5, label="Z") |
|
|
|
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
img_output = gr.Image(type="filepath", label="Simulation View") |
|
|
text_output = gr.Textbox(label="Joint States", lines=10) |
|
|
|
|
|
|
|
|
def live_update(*vals): |
|
|
joints = list(vals[:7]) |
|
|
grip = vals[7] |
|
|
cam = [vals[8], vals[9], vals[10]] |
|
|
tgt = [vals[11], vals[12], vals[13]] |
|
|
return render_sim(joints, grip, cam, tgt) |
|
|
|
|
|
sliders = joint_sliders + [gripper, cam_x, cam_y, cam_z, tgt_x, tgt_y, tgt_z] |
|
|
for s in sliders: |
|
|
s.change(fn=live_update, inputs=sliders, outputs=[img_output, text_output]) |
|
|
|
|
|
gr.Button("π Reset Robot").click( |
|
|
fn=lambda: render_sim([0]*7, 0.02, [1.5, 0, 1.0], [0, 0, 0.5]), |
|
|
inputs=[], outputs=[img_output, text_output] |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### βοΈ Move Robot to Custom Joint Angles") |
|
|
joint_input_box = gr.Textbox(label="Enter 7 Joint Angles (comma-separated)") |
|
|
gr.Button("βΆοΈ Move to Angles").click( |
|
|
fn=lambda s, x, y, z, tx, ty, tz: move_to_input_angles(s, [x, y, z], [tx, ty, tz]), |
|
|
inputs=[joint_input_box, cam_x, cam_y, cam_z, tgt_x, tgt_y, tgt_z], |
|
|
outputs=[img_output, text_output] |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### π§Ύ Pick and Place Input (3 sets of joint angles)") |
|
|
|
|
|
with gr.Row(): |
|
|
position_input = gr.Textbox(label="Object Position Angles") |
|
|
gr.Button("π Copy").click(fn=copy_current_joint_angles, inputs=joint_sliders, outputs=position_input) |
|
|
|
|
|
with gr.Row(): |
|
|
approach_input = gr.Textbox(label="Approach Angles") |
|
|
gr.Button("π Copy").click(fn=copy_current_joint_angles, inputs=joint_sliders, outputs=approach_input) |
|
|
|
|
|
with gr.Row(): |
|
|
place_input = gr.Textbox(label="Place Angles") |
|
|
gr.Button("π Copy").click(fn=copy_current_joint_angles, inputs=joint_sliders, outputs=place_input) |
|
|
|
|
|
gr.Button("π€ Perform Pick and Place").click( |
|
|
fn=lambda p, a, pl, x, y, z, tx, ty, tz: pick_and_place(p, a, pl, [x, y, z], [tx, ty, tz]), |
|
|
inputs=[position_input, approach_input, place_input, cam_x, cam_y, cam_z, tgt_x, tgt_y, tgt_z], |
|
|
outputs=[img_output, text_output] |
|
|
) |
|
|
|
|
|
demo.launch(debug=True) |
|
|
|
|
|
|
|
|
|