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

  1from dataclasses import dataclass
  2import math
  3import random
  4
  5from jacobi import Box, Obstacle, Frame, Motion, LinearSection, Planner
  6from jacobi.drivers import SimulatedDriver
  7
  8
  9class Container:
 10    """State of a container to get the next box position."""
 11
 12    def __init__(self, per_layer: int, number_layers: int) -> None:
 13        self.per_layer = per_layer
 14        self.number_layers = number_layers
 15        self.capacity = self.per_layer * self.number_layers
 16        self.available_spaces = self.capacity
 17        self.next_position = {'layer': 0, 'box_in_layer': 0}
 18
 19    def add(self) -> tuple[int, int]:
 20        if self.available_spaces <= 0:
 21            return None
 22
 23        position = self.next_position.copy()
 24        self.available_spaces -= 1
 25
 26        # Update next_position
 27        self.next_position['box_in_layer'] += 1
 28        if self.next_position['box_in_layer'] == self.per_layer:
 29            self.next_position['box_in_layer'] = 0
 30            self.next_position['layer'] += 1
 31
 32        return position['layer'], position['box_in_layer']
 33
 34
 35@dataclass
 36class BoxesForContainer:
 37    """All info about palletizing a box into a container."""
 38
 39    box: Obstacle
 40    obstacle: Obstacle
 41    container: Container
 42    place_frames: list[Frame]
 43    place_linear: list[Frame]
 44
 45    @staticmethod
 46    def generate_box_order(number_small: int, info_small, number_big: int, info_big) -> list:
 47        box_order = [info_small] * number_small + [info_big] * number_big
 48        random.shuffle(box_order)
 49        return box_order
 50
 51
 52def run_application():
 53    # Load project from Studio
 54    planner = Planner.load_from_studio('Cage and Gaylord Palletizing')
 55    robot = planner.environment.get_robot()
 56
 57    # Get waypoints for motion planning
 58    conveyor_feed_rot = math.radians(90)
 59    home = planner.environment.get_waypoint('Home')
 60    surface_pick = planner.environment.get_waypoint('Pick')
 61    roller_cage = planner.environment.get_obstacle('cage_bottom_collision')
 62    gaylord_box = planner.environment.get_obstacle('GL_bottom_collision')
 63
 64    # Boxes and containers
 65    container_roller_cage = Container(per_layer=4, number_layers=4)
 66    container_gaylord_box = Container(per_layer=3, number_layers=5)
 67
 68    # Clearances
 69    stack_clearance = 0.001  # [m]
 70    rc_clearance = 0.0175  # [m]
 71    gl_clearance = 0.0175  # [m]
 72
 73    # Dimensions and box positions
 74    small_box_x = (roller_cage.collision.x - rc_clearance * (container_roller_cage.per_layer + 1)) / (container_roller_cage.per_layer)
 75    big_box_x = (gaylord_box.collision.x - gl_clearance * (container_gaylord_box.per_layer + 1)) / (container_gaylord_box.per_layer)
 76    small_box_geometry = Box(small_box_x, roller_cage.collision.y - 0.150, 0.380)
 77    big_box_geometry = Box(big_box_x, gaylord_box.collision.y - 0.075, 0.1675)
 78
 79    roller_cage_frames = [
 80        Frame(x=((roller_cage.collision.x / 2) - (1 * rc_clearance) - (small_box_geometry.x / 2))),
 81        Frame(x=((roller_cage.collision.x / 2) - (2 * rc_clearance) - (3 * small_box_geometry.x / 2))),
 82        Frame(x=((roller_cage.collision.x / 2) - (3 * rc_clearance) - (5 * small_box_geometry.x / 2))),
 83        Frame(x=((roller_cage.collision.x / 2) - (4 * rc_clearance) - (7 * small_box_geometry.x / 2))),
 84    ]
 85
 86    gaylord_box_frames = [
 87        Frame(x=((gaylord_box.collision.x / 2) - (1 * gl_clearance) - (big_box_geometry.x / 2))),
 88        Frame(x=((gaylord_box.collision.x / 2) - (2 * gl_clearance) - (3 * big_box_geometry.x / 2))),
 89        Frame(x=((gaylord_box.collision.x / 2) - (3 * gl_clearance) - (5 * big_box_geometry.x / 2))),
 90    ]
 91
 92    # Linear retraction/approach parameters, layer-wise
 93    pick_linear = Frame(z=0.05)  # [m]
 94    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]
 95    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]
 96
 97    # Container info
 98    info_small = BoxesForContainer(
 99        box=Obstacle('small_box', small_box_geometry, Frame(), color='B99976'),
100        obstacle=roller_cage,
101        container=container_roller_cage,
102        place_frames=roller_cage_frames,
103        place_linear=rc_place_linear,
104    )
105
106    info_big = BoxesForContainer(
107        box=Obstacle('big_box', big_box_geometry, Frame(), color='987554'),
108        obstacle=gaylord_box,
109        container=container_gaylord_box,
110        place_frames=gaylord_box_frames,
111        place_linear=gl_place_linear,
112    )
113
114    box_order = BoxesForContainer.generate_box_order(
115        number_small=container_roller_cage.capacity, info_small=info_small,
116        number_big=container_gaylord_box.capacity, info_big=info_big,
117    )
118
119    # Set and reset Studio project for visualization
120    driver = SimulatedDriver(planner, sync_with_studio=True)
121    driver.set_current_joint_position(home.position)
122    driver.studio.reset()
123    driver.studio.set_item(None)
124
125    for idx, info in enumerate(box_order):
126        # Pick position, top of box surface
127        box_height = info.box.collision.z
128        info.box.name = f'box_{idx}'
129
130        pick = surface_pick.position * Frame(z=box_height)
131        box_at_pick = info.box.with_origin(pick * Frame(z=-box_height / 2, c=conveyor_feed_rot))
132        driver.studio.add_obstacle(box_at_pick)  # spawn box at pick
133
134        # Finding place location
135        layer, box_in_layer = info.container.add()
136        layer_frame = info.obstacle.origin * Frame(z=info.obstacle.collision.z / 2) * Frame(z=(box_height + stack_clearance) * layer)
137        place_frame = layer_frame * info.place_frames[box_in_layer] * Frame(z=box_height + stack_clearance)
138
139        if idx == 0:
140            # Define home -> pick motion
141            motion = Motion('home_to_pick', driver.current_joint_position, pick)
142            motion.linear_approach = LinearSection(offset=Frame(z=0.20), speed=0.15)
143
144        else:
145            # Define place -> pick motion
146            motion = Motion(f'place_to_pick_{idx}', driver.current_joint_position, pick)
147            motion.linear_retraction = LinearSection(offset=info.place_linear[layer])
148            motion.linear_approach = LinearSection(offset=pick_linear)
149
150        # Plan and execute pick -> place motion
151        trajectory = planner.plan(motion)
152        driver.run(trajectory)
153
154        # Grasp the box, and update collision model and Studio
155        driver.studio.remove_obstacle(box_at_pick)
156        box_as_item = info.box.with_origin(Frame(z=-box_height / 2, c=conveyor_feed_rot))
157        robot.item_obstacle = box_as_item
158        driver.studio.set_item(box_as_item)
159
160        # Define pick -> place motion
161        motion = Motion(f'pick_to_place_{idx}', driver.current_joint_position, place_frame * Frame(c=-conveyor_feed_rot))
162        motion.linear_retraction = LinearSection(offset=pick_linear)
163        motion.linear_approach = LinearSection(offset=info.place_linear[layer])
164        motion.orientation_loss_weight = 2.0
165        motion.path_length_loss_weight = 2.0
166
167        # Plan and execute pick -> place motion
168        trajectory = planner.plan(motion)
169        driver.run(trajectory)
170
171        # Release box from the robot
172        robot.item_obstacle = None
173        driver.studio.set_item(None)
174
175        # Add box at place position to collision model and Studio
176        box_at_place = info.box.with_origin(place_frame * Frame(z=-box_height / 2))
177        planner.environment.add_obstacle(box_at_place)
178        driver.studio.add_obstacle(box_at_place)
179
180
181if __name__ == '__main__':
182    run_application()