Container Palletizing¶
Palletizing boxes into storage containers makes programming motion the classical way more challenging, as more obstacles within the environment needs to be considered. With Jacobi, planning motions becomes as easy as with a simple pallet.
This example projects picks different boxes from a conveyor belt and places them to their according container, either a roller cage or a gaylord box container.
To load the Jacobi Studio project automatically in the background, please define your JACOBI_API_KEY
environment variable.
Video¶
Application Code¶
from dataclasses import dataclass
import math
import random
from jacobi import Box, Obstacle, Frame, Motion, LinearSection, Planner
from jacobi.drivers import SimulatedDriver
class Container:
"""State of a container to get the next box position."""
def __init__(self, per_layer: int, number_layers: int) -> None:
self.per_layer = per_layer
self.number_layers = number_layers
self.capacity = self.per_layer * self.number_layers
self.available_spaces = self.capacity
self.next_position = {'layer': 0, 'box_in_layer': 0}
def add(self) -> tuple[int, int]:
if self.available_spaces <= 0:
return None
position = self.next_position.copy()
self.available_spaces -= 1
# Update next_position
self.next_position['box_in_layer'] += 1
if self.next_position['box_in_layer'] == self.per_layer:
self.next_position['box_in_layer'] = 0
self.next_position['layer'] += 1
return position['layer'], position['box_in_layer']
@dataclass
class BoxesForContainer:
"""All info about palletizing a box into a container."""
box: Obstacle
obstacle: Obstacle
container: Container
place_frames: list[Frame]
place_linear: list[Frame]
@staticmethod
def generate_box_order(number_small: int, info_small, number_big: int, info_big) -> list:
box_order = [info_small] * number_small + [info_big] * number_big
random.shuffle(box_order)
return box_order
def run_application():
# Load project from Studio
planner = Planner.load_from_studio('Cage and Gaylord Palletizing')
robot = planner.environment.get_robot()
# Get waypoints for motion planning
conveyor_feed_rot = math.radians(90)
home = planner.environment.get_waypoint('Home')
surface_pick = planner.environment.get_waypoint('Pick')
roller_cage = planner.environment.get_obstacle('cage_bottom_collision')
gaylord_box = planner.environment.get_obstacle('GL_bottom_collision')
# Boxes and containers
container_roller_cage = Container(per_layer=4, number_layers=4)
container_gaylord_box = Container(per_layer=3, number_layers=5)
# Clearances
stack_clearance = 0.001 # [m]
rc_clearance = 0.0175 # [m]
gl_clearance = 0.0175 # [m]
# Dimensions and box positions
small_box_x = (roller_cage.collision.x - rc_clearance * (container_roller_cage.per_layer + 1)) / (container_roller_cage.per_layer)
big_box_x = (gaylord_box.collision.x - gl_clearance * (container_gaylord_box.per_layer + 1)) / (container_gaylord_box.per_layer)
small_box_geometry = Box(small_box_x, roller_cage.collision.y - 0.150, 0.380)
big_box_geometry = Box(big_box_x, gaylord_box.collision.y - 0.075, 0.1675)
roller_cage_frames = [
Frame(x=((roller_cage.collision.x / 2) - (1 * rc_clearance) - (small_box_geometry.x / 2))),
Frame(x=((roller_cage.collision.x / 2) - (2 * rc_clearance) - (3 * small_box_geometry.x / 2))),
Frame(x=((roller_cage.collision.x / 2) - (3 * rc_clearance) - (5 * small_box_geometry.x / 2))),
Frame(x=((roller_cage.collision.x / 2) - (4 * rc_clearance) - (7 * small_box_geometry.x / 2))),
]
gaylord_box_frames = [
Frame(x=((gaylord_box.collision.x / 2) - (1 * gl_clearance) - (big_box_geometry.x / 2))),
Frame(x=((gaylord_box.collision.x / 2) - (2 * gl_clearance) - (3 * big_box_geometry.x / 2))),
Frame(x=((gaylord_box.collision.x / 2) - (3 * gl_clearance) - (5 * big_box_geometry.x / 2))),
]
# Linear retraction/approach parameters, layer-wise
pick_linear = Frame(z=0.05) # [m]
rc_place_linear = [Frame(z=0.650), Frame(z=0.500), Frame(z=0.350), Frame(z=0.200), Frame(z=0.150), Frame(z=0.125)] # [m]
gl_place_linear = [Frame(z=0.490), Frame(z=0.450), Frame(z=0.380), Frame(z=0.345), Frame(z=0.175), Frame(z=0.150)] # [m]
# Container info
info_small = BoxesForContainer(
box=Obstacle('small_box', small_box_geometry, Frame(), color='B99976'),
obstacle=roller_cage,
container=container_roller_cage,
place_frames=roller_cage_frames,
place_linear=rc_place_linear,
)
info_big = BoxesForContainer(
box=Obstacle('big_box', big_box_geometry, Frame(), color='987554'),
obstacle=gaylord_box,
container=container_gaylord_box,
place_frames=gaylord_box_frames,
place_linear=gl_place_linear,
)
box_order = BoxesForContainer.generate_box_order(
number_small=container_roller_cage.capacity, info_small=info_small,
number_big=container_gaylord_box.capacity, info_big=info_big,
)
# Set and reset Studio project for visualization
driver = SimulatedDriver(planner, sync_with_studio=True)
driver.set_current_joint_position(home.position)
driver.studio.reset()
driver.studio.set_item(None)
for idx, info in enumerate(box_order):
# Pick position, top of box surface
box_height = info.box.collision.z
info.box.name = f'box_{idx}'
pick = surface_pick.position * Frame(z=box_height)
box_at_pick = info.box.with_origin(pick * Frame(z=-box_height / 2, c=conveyor_feed_rot))
driver.studio.add_obstacle(box_at_pick) # spawn box at pick
# Finding place location
layer, box_in_layer = info.container.add()
layer_frame = info.obstacle.origin * Frame(z=info.obstacle.collision.z / 2) * Frame(z=(box_height + stack_clearance) * layer)
place_frame = layer_frame * info.place_frames[box_in_layer] * Frame(z=box_height + stack_clearance)
if idx == 0:
# Define home -> pick motion
motion = Motion('home_to_pick', driver.current_joint_position, pick)
motion.linear_approach = LinearSection(offset=Frame(z=0.20), speed=0.15)
else:
# Define place -> pick motion
motion = Motion(f'place_to_pick_{idx}', driver.current_joint_position, pick)
motion.linear_retraction = LinearSection(offset=info.place_linear[layer])
motion.linear_approach = LinearSection(offset=pick_linear)
# Plan and execute pick -> place motion
trajectory = planner.plan(motion)
driver.run(trajectory)
# Grasp the box, and update collision model and Studio
driver.studio.remove_obstacle(box_at_pick)
box_as_item = info.box.with_origin(Frame(z=-box_height / 2, c=conveyor_feed_rot))
robot.item_obstacle = box_as_item
driver.studio.set_item(box_as_item)
# Define pick -> place motion
motion = Motion(f'pick_to_place_{idx}', driver.current_joint_position, place_frame * Frame(c=-conveyor_feed_rot))
motion.linear_retraction = LinearSection(offset=pick_linear)
motion.linear_approach = LinearSection(offset=info.place_linear[layer])
motion.orientation_loss_weight = 2.0
motion.path_length_loss_weight = 2.0
# Plan and execute pick -> place motion
trajectory = planner.plan(motion)
driver.run(trajectory)
# Release box from the robot
robot.item_obstacle = None
driver.studio.set_item(None)
# Add box at place position to collision model and Studio
box_at_place = info.box.with_origin(place_frame * Frame(z=-box_height / 2))
planner.environment.add_obstacle(box_at_place)
driver.studio.add_obstacle(box_at_place)
if __name__ == '__main__':
run_application()