Pallet layer offset

Hi, I have a UR10 of CB3 and I need help to create a pallet. I have to create a pallet of 2x2 boxes and 4 layers, so 16 boxes in total. In each box there I have to place 16 bottles in 4x4. The operators have to place the empty boxes on the pallet, and then the cobot places the bottles in the boxes.

I’m using the pallet function, but I can’t just simply use the ‘box’ configuration for the full pallet, or even use the ‘square’ configuration for a full layer, I have to use the ‘square’ configuration for each boxes. Right now I finished the first layer of the pallet. I made my program like this:

loop 16:

  • call a function to pick up a bottle
  • pallet function with square configuration (so 4 corners)

then repeat that 3 times for the 3 other boxes of the layer

so I have 4 approach points, 4 exit points and 4 pattern points.
I’d like to just put an offset on these points, and put a big loop 4 in which I would put the 4 loop 16, and everytime I finish a layer I would just add the offset to the points so I don’t have to define all the corners everytime. I just can’t find a way to do that, so maybe it’s not the best way idk. Any suggestions ?

If you programmed everything to a Feature, you should be able to just do something like

myFeature = pose_add(myFeature_const, p[xOffset, yOffset, zOffset, 0,0,0])

and your whole program will shift. I tend to not like the built-in palletizing node because it’s got its own problems. I usually just shift a Feature around myself to achieve it.

Yeah I’m new to this environment but I feel that the function Pallet is missing some useful functionalities. I will try your way thanks!

I’m new to Universal Robots so, would you do something like that:
set in the Feature:

  • pallet_plane
  • point_first_bottle
  • y_offset_box
  • y_offset_bottle
  • x_offset_box
  • x_offset_bottle
  • z_offset

and then with some loops, modify the point where to drop the bottle with the offsets ? does that sound good ?

Yep. You pretty much nailed it exactly.

Your exact implementation of it can vary, but I typically just input my row spacing and column spacing and then always start from the Feature_const value and add from there each time.

To determine the placement, you just need to use division and modulo (%). Using my crappy Paint drawing as an example, to algorithmically place the circled part, I would take the part I’m interested in and divide by the number of columns to get the row, and the part % columns to get the column.

Example: the part I circled is the 6th part, if I started in the bottom left, and went bottom to top, calling the 1st part Part 0 (needed for the math to work out)
So 6 / 4 = 1.5 (just ditch the decimal, we only care about whole numbers). So 6/4 = 1. That means we are on the 1st row (index it starting at 0). And 6 mod 4 = 2, so we are on the 2nd column (again start at 0).

A good way to understand the indexing is to ask yourself “If i did all my programmed points on this first part (the bottom left part), how many shifts right and up do I have to go to place the rest of them?”

So if we defined our spacing as 4 inches left to right and 2 inches top and bottom (again ignore the inch to meter conversion here), we can place any bottle by saying:

rows = 4
xShift = 4
yShift = 2
currentPart = 0
totalParts = 16
while(currentPart < totalParts)
{
//do your bottle place routine with all Waypoints referencing myFeature
currentPart = currentPart + 1
myFeature = pose_add(myFeature_const, p[yShift*currentPart%rows, xShift*(floor(currentPart/rows)), 0, 0,0,0]
}

Then you’d have ALL of that in another loop that looked at a boxCount variable. Shift the Feature by your BoxOffset and run it all again.

This also assumes myFeature is the same orientation as Base. If it’s not, you just have to shift with respect to myFeature’s orientation instead.

Once you get this structure ironed out for one application, it can be expanded and modulated very easily, because it’s controlled entirely by 4 inputs: Number of rows/columns, and distance between rows/columns

1 Like

Sounds like this makes it even easier, thanks a lot!

Sorry I have a basic question but I can’t find the answer and I don’t have so much time anymore.

In the Feature I created a plane “Pallet”.
I’ve set a point “drop_bottle_fix” not variable and “drop_bottle” variable.
“drop_bottle_fix” has as coordinates something like x = 130mm, y = 130mm, z = 250mm in the “Pallet” Feature.

In my code (to test) I did:

assignment: offset_bottle:= 100 //100mm between each bottles in x and y
loop 4:
     assignment: drop_bottle:= pose_add(drop_bottle_fix_const, p[offset_bottle * loop_1, 0, 0, 0, 0, 0,])
     moveL (with "Pallet" selected for the Feature) : drop_bottle
     moveJ: start_point

I got some errors, so I took a look at the variables, and then I understood that in the program the points don’t take the coordinates as millimeters as when we configure the point manually.
So I modified offset_bottle from 100 to 0.1 and it was better (not sure if the conversion is good though).
At the beginning I forgot to put “Pallet” for the feature of moveL, I changed that and the cobot tried to go through the floor.
Now I’m thinking: when I use the function pose_add, there is nowhere to put in which system of coordinates I’m adding offset_bottle, if it’s in the system “Pallet” or “base”, and I’m not able to find my answer. And I also don’t understand why in Variables my points have values like p[0.39054, 0.48311, 0.08509, …] instead of p[130, 130, 250, …]. I understand that it’s not in millimeters but I don’t know how to convert a offset of 100mm to this system.

Can you please enlighten me on this?

Yep welcome to the pain of multiple coordinate systems. Your use case here can teach you a lot of fundamental skills which can open doors for future applications.

1: The robot sees everything in METERS and RADIANS. So get used to dividing everything by 1000 if you’re using millimeters or 39.37 in you’re using inches.

2: The robot takes all shift values (and displays them) in BASE frame. This is why you don’t see “130 130 250” if you were wanting to see “I shifted 130m in x and y and 250m in z”

3: Shifting with respect to a DIFFERENT Feature is almost always what you want to do, and to be honest is needlessly complex. For this, I almost always use this function:

def shiftRelativeToFeature(x_offset, y_offset, z_offset, feature, poseToShift):
  return pose_trans(feature, pose_add(pose_trans(pose_inv(feature), poseToShift), p[x_offset, y_offset, z_offset,0,0,0]))
end

I just insert a script node in the before start, paste that in there, and then I can call “shiftRelativeToFeature(0.130, 0.130, 0.250, Pallet, myPoseThatNeedsShifting)” and it will return a pose (in BASE coordinates) that is equivalent to this. So you can just set “Pallet” equal to the result. (I believe the function might be somewhat redundant, as in you can get what you want with a single pose_tans() command, but I like the explicit-ness of this function.)

4: You said you changed the Feature and the robot wanted to go through the floor. You CANNOT change Features without reteaching the waypoint. What you want to do is switch it back to BASE, then jog to that point. THEN change the Feature to Pallet and THEN set the waypoint again (to the exact same place). This will match that Waypoint to that Feature. You’d have to do this with every waypoint.

1 Like

The cobot is in a plant where I’m not based and I can’t go often, so that’s why I need to understand quickly how it works.

Thanks a lot for your explanation everything is clear. I left the plant friday and I’ll go back in a couple of weeks, I’ll try it then but I feel it’s the last point I was missing to make it works.