Depalletize PVC Pipes
SUMMARY
Vision-guided picking and depalletizing of PVC pipes from a pallet
Code
INFO
The following code was generated by the Tzara agent.
python
# Pipeline: Pick PVC pipes from a grid and place them gently into a paper box.
# Strategy:
# - Connect to UR10E + OnRobot RG2.
# - Set TCP offset (23 cm from flange).
# - Iterate over a hardcoded grid of pipe positions.
# - For each pipe: approach above, descend, close gripper, lift, move above box,
# descend with move_until_contact (gentle stop), open gripper, retreat.
# - Cleanup hardware on success or failure.
from loguru import logger
import numpy as np
from telekinesis.synapse.robots.manipulators.universal_robots import UniversalRobotsUR10E
from telekinesis.synapse.tools.parallel_grippers.onrobot import OnRobotRG2
# -----------------------------------------------------------------------------
# Tunable constants
# -----------------------------------------------------------------------------
# Hardware
ROBOT_IP = "192.168.2.2"
GRIPPER_IP = "192.168.1.1"
TCP_OFFSET = [0.0, 0.0, 0.02, 0.0, 0.0, 0.0] # 23 cm along flange Z, no rotation
# Grid of PVC pipes on the table (base frame, meters; orientation in degrees).
# Grid origin = pose above pipe at row=0, col=0 (top of pipe contact point).
GRID_ORIGIN_XYZ = [0.2341, 0.78192, 0.29442] # x, y, z [m]
GRID_TOOL_RPY = [-180.0, 0.0, 90.0] # tool pointing down
GRID_ROW_DELTA = [0.00, -0.10997, 0.00] # +Y per row [m]
GRID_COL_DELTA = [ -0.10161, 0.00, 0.00] # +X per col [m]
GRID_ROWS = 2
GRID_COLS = 3
# Heights / clearances
APPROACH_HEIGHT = 0.25 # how far above pipe top to pre-position [m]
GRASP_DESCENT_OFFSET = 0.00 # additional descent past grid Z to grasp [m]
LIFT_HEIGHT = 0.25 # how high to lift after grasping [m]
# Drop box (paper packaging box) — pose above box opening.
BOX_ABOVE_POSE = [-0.523563, -0.001, 0.47432, -180.0, 0.0, -180.0] # x,y,z,rx,ry,rz
# Direction for move_until_contact placement (downward along base -Z).
PLACE_CONTACT_DIRECTION = [0.0, 0.0, -1.0, 0.0, 0.0, 0.0]
PLACE_CONTACT_SPEED = [0.0, 0.0, -0.03, 0.0, 0.0, 0.0] # 5 cm/s downward
PLACE_CONTACT_ACCEL = 0.2
# Motion params
LINEAR_SPEED = 0.55 # m/s for moveL
LINEAR_ACCEL = .5 # m/s^2
# Gripper params (OnRobot RG2 — width in mm, force in N)
GRIPPER_OPEN_WIDTH_MM = 100.0
GRIPPER_CLOSE_WIDTH_MM = 20.0 # tuned for PVC pipe outer diameter
GRIPPER_GRASP_FORCE_N = 20.0
GRIPPER_RELEASE_FORCE_N = 40.0
# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
def grid_pose_for(row: int, col: int) -> list[float]:
"""Compute the [x,y,z,rx,ry,rz] pose at the top of the pipe at (row,col)."""
x = GRID_ORIGIN_XYZ[0] + row * GRID_ROW_DELTA[0] + col * GRID_COL_DELTA[0]
y = GRID_ORIGIN_XYZ[1] + row * GRID_ROW_DELTA[1] + col * GRID_COL_DELTA[1]
z = GRID_ORIGIN_XYZ[2] + row * GRID_ROW_DELTA[2] + col * GRID_COL_DELTA[2]
return [x, y, z, *GRID_TOOL_RPY]
def offset_z(pose: list[float], dz: float) -> list[float]:
"""Return pose with Z offset by dz (meters)."""
p = list(pose)
p[2] += dz
return p
# -----------------------------------------------------------------------------
# Main pipeline
# -----------------------------------------------------------------------------
robot = UniversalRobotsUR10E()
gripper = OnRobotRG2()
robot_connected = False
gripper_connected = False
try:
# 1. Connect hardware
logger.info(f"Connecting to UR10E at {ROBOT_IP} ...")
robot.connect(ROBOT_IP)
robot_connected = True
logger.info("UR10E connected.")
logger.info(f"Connecting to OnRobot RG2 at {GRIPPER_IP} ...")
gripper.connect(GRIPPER_IP, protocol="MODBUS_TCP")
gripper_connected = True
logger.info("OnRobot RG2 connected.")
# 2. Configure TCP offset (23 cm from flange)
logger.info(f"Setting TCP offset: {TCP_OFFSET}")
robot.set_tcp(TCP_OFFSET)
# 3. Open gripper to start in known state
logger.info(f"Opening gripper to {GRIPPER_OPEN_WIDTH_MM} mm.")
gripper.move(position=GRIPPER_OPEN_WIDTH_MM, force=GRIPPER_RELEASE_FORCE_N)
# 4. Iterate grid positions and pick-and-place each pipe
pipe_index = 0
total_pipes = GRID_ROWS * GRID_COLS
logger.info(f"Beginning pick-and-place for {total_pipes} pipes "
f"({GRID_ROWS} rows x {GRID_COLS} cols).")
for row in range(GRID_ROWS):
for col in range(GRID_COLS):
pipe_index += 1
pipe_top_pose = grid_pose_for(row, col)
approach_pose = offset_z(pipe_top_pose, APPROACH_HEIGHT)
grasp_pose = offset_z(pipe_top_pose, GRASP_DESCENT_OFFSET)
lift_pose = offset_z(pipe_top_pose, LIFT_HEIGHT)
logger.info(f"[Pipe {pipe_index}/{total_pipes}] "
f"row={row}, col={col}, top_pose={pipe_top_pose}")
# 4a. Move above pipe
logger.info(f"[Pipe {pipe_index}] Moving to approach pose {approach_pose}.")
robot.set_cartesian_pose(approach_pose,
speed=LINEAR_SPEED,
acceleration=LINEAR_ACCEL)
# 4b. Open gripper before descending (ensure clear)
logger.info(f"[Pipe {pipe_index}] Pre-opening gripper to "
f"{GRIPPER_OPEN_WIDTH_MM} mm.")
gripper.open(force=GRIPPER_RELEASE_FORCE_N)
# 4c. Descend to grasp pose
logger.info(f"[Pipe {pipe_index}] Descending to grasp pose {grasp_pose}.")
robot.set_cartesian_pose(grasp_pose,
speed=LINEAR_SPEED,
acceleration=LINEAR_ACCEL)
# 4d. Close gripper to grasp
logger.info(f"[Pipe {pipe_index}] Closing gripper to "
f"{GRIPPER_CLOSE_WIDTH_MM} mm @ {GRIPPER_GRASP_FORCE_N} N.")
grasp_status = gripper.close(force=GRIPPER_GRASP_FORCE_N)
logger.info(f"[Pipe {pipe_index}] Grasp status: {grasp_status}")
# 4e. Lift pipe
logger.info(f"[Pipe {pipe_index}] Lifting to {lift_pose}.")
robot.set_cartesian_pose(lift_pose,
speed=LINEAR_SPEED,
acceleration=LINEAR_ACCEL)
# 4f. Move above the paper box
logger.info(f"[Pipe {pipe_index}] Moving above box at {BOX_ABOVE_POSE}.")
robot.set_cartesian_pose(BOX_ABOVE_POSE,
speed=LINEAR_SPEED,
acceleration=LINEAR_ACCEL)
# 4g. Gentle descent into box using move_until_contact
logger.info(f"[Pipe {pipe_index}] Descending into box with "
f"move_until_contact (gentle placement).")
# robot.move_until_contact(cartesian_speed=PLACE_CONTACT_SPEED,
# direction=PLACE_CONTACT_DIRECTION,
# acceleration=PLACE_CONTACT_ACCEL)
logger.info(f"[Pipe {pipe_index}] Contact detected; placement reached.")
# 4h. Open gripper to release pipe gently
logger.info(f"[Pipe {pipe_index}] Opening gripper to release pipe.")
release_status = gripper.open(force=GRIPPER_RELEASE_FORCE_N)
logger.info(f"[Pipe {pipe_index}] Release status: {release_status}")
# 4i. Retreat back above box to clear
logger.info(f"[Pipe {pipe_index}] Retreating above box.")
robot.set_cartesian_pose(BOX_ABOVE_POSE,
speed=LINEAR_SPEED,
acceleration=LINEAR_ACCEL)
logger.info(f"[Pipe {pipe_index}] Done.")
logger.info("All pipes have been picked and placed successfully.")
except Exception as e:
logger.exception(f"Pipeline failed: {e}")
# Best-effort: stop any ongoing motion before disconnecting.
try:
if robot_connected:
logger.warning("Stopping robot motion due to failure.")
robot.stop_cartesian_motion()
except Exception as stop_err:
logger.error(f"Failed to stop robot motion cleanly: {stop_err}")
raise
finally:
# Cleanup hardware regardless of success/failure.
if gripper_connected:
try:
logger.info("Disconnecting gripper.")
gripper.disconnect()
except Exception as gerr:
logger.error(f"Error disconnecting gripper: {gerr}")
if robot_connected:
try:
logger.info("Disconnecting robot.")
robot.disconnect()
except Exception as rerr:
logger.error(f"Error disconnecting robot: {rerr}")
logger.info("Pipeline cleanup complete.")
