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()