BRAND.05 / CB3 + e-Series
The script language under every Universal Robots arm.
URScript is what the controller runs. A PolyScope program built from nodes on the teach pendant is compiled into URScript before it executes, and you can also send script to the controller over the network. It covers the CB3 arms (UR3, UR5, UR10), the e-Series (UR3e, UR5e, UR10e, UR16e) and the newer UR15, UR18, UR20 and UR30. The syntax looks like Python, but blocks close with a literal end, indentation is cosmetic, and there is no for loop. Units are SI: metres, radians, seconds, m/s and rad/s. Motion is movej / movel / movep / movec, I/O is set_digital_out() / get_digital_in(), and timing is sleep() and sync().
A UR arm has force, power, speed and momentum limits, safety planes and other safety functions, set in the safety configuration and kept apart from the program. They do not make an application collaborative on their own. The integrator still carries out a risk assessment of the whole application, including the tool, the part, the fixtures and the people nearby, to ISO 10218-2 and, where people share the workspace, ISO/TS 15066. A sharp gripper finger on a cobot is as dangerous as on any other robot. Nothing in a script replaces that safety work.
A script is a list of statements and def blocks. def name(): ... end defines a function and name() calls it. A PolyScope program is compiled into one def program_name(): ... end block, which the controller runs from the top. Variables assigned at the top of that block are visible inside its functions; assigning to a name inside a function makes a local variable unless the name is declared global.
On a real cellPut shared motion in functions and keep the main program short enough to read on the pendant. A Script node in PolyScope can load a .script file, so several programs can call the same pick routine.
def pick(): movel(p_pick, a=1.2, v=0.25) end def main(): pick() end main()
movej moves in joint space, with a in rad/s² and v in rad/s (defaults 1.4 and 1.05). Give it a pose and the controller works out the joint angles. movel moves the TCP (tool centre point) in a straight line, with a in m/s² and v in m/s (defaults 1.2 and 0.25, so 250 mm/s). movep keeps a constant tool speed through a run of waypoints and blends between them, which suits gluing and dispensing. movec draws an arc through a via pose to an end pose. The r argument is a blend radius in metres: with r=0.02 the robot starts on the next move 20 mm before it reaches the target.
On a real cellv is m/s for movel and rad/s for movej. Someone used to mm/s who writes v=250 has asked for 250 m/s; write v=0.25. Use r=0 on the move that ends at a pick or place, so the robot actually reaches the point before the gripper switches.
movej(p_home, a=1.4, v=1.05) movel(p_pick, a=1.2, v=0.25) movec(p_via, p_to, a=1.2, v=0.25, r=0)
A pose is p[x, y, z, rx, ry, rz]: position in metres and rotation as an axis-angle rotation vector in radians. The vector points along the rotation axis and its length is the angle, so p[0.4, 0, 0.3, 0, 3.1416, 0] is the tool turned 180° about Y, pointing straight down. It is not roll, pitch and yaw, although the pendant can display RPY. get_actual_tcp_pose() returns the measured TCP pose in the base frame. pose_trans(p_frame, p_offset) applies an offset expressed in another frame, such as the tool frame; pose_add adds the positions and composes the rotations in the base frame.
On a real cellCopying roll, pitch and yaw from a CAD system into rx, ry, rz gives the wrong orientation, and you only see it when the wrist turns somewhere unexpected. Convert with rpy2rotvec() first.
p_pick = p[0.4, 0.0, 0.2, 0, 3.1416, 0] p_above = pose_trans(p_pick, p[0, 0, -0.05, 0, 0, 0]) movel(p_above, a=1.2, v=0.25) movel(p_pick, a=0.5, v=0.05)
set_tcp(pose) sets the TCP relative to the tool flange. set_target_payload(mass, cog, inertia) gives the controller the mass in kg, the centre of gravity in metres and the inertia matrix. It replaces set_payload(m, cog), which is deprecated since software 5.10. The controller uses the payload in its dynamic model and its force and collision checks.
On a real cellSet the payload when the part is gripped and again when it is released. A payload that does not match what the arm is carrying throws off those checks and can show up as protective stops.
set_tcp(p[0, 0, 0.15, 0, 0, 0]) set_target_payload(1.5, [0, 0, 0.05], [0.002, 0.002, 0.002, 0, 0, 0])
set_digital_out(n, True) drives standard output n (0 to 7) in the control box and get_digital_in(n) reads standard input n. The tool flange has its own I/O: set_tool_digital_out(n, b) and get_tool_digital_in(n) with n 0 or 1. Analog outputs take a fraction of their range: set_analog_out(0, 0.5) gives 5 V, or 12 mA if the port is set to current.
On a real cellAn empty wait loop such as while not get_digital_in(0): end never gives time back, and the controller stops the program for using too much time. Put sync() or sleep() inside every wait loop.
set_digital_out(1, True) while not get_digital_in(0): sync() end set_tool_digital_out(0, True)
if cond: ... elif cond: ... else: ... end, while cond: ... end and def name(): ... end all close with the word end, and indentation is only for the reader. break and continue work inside while loops, return leaves a function and halt stops the program. URScript has no for loop: PolyScope turns a Loop node into a while with a counter, and hand-written scripts do the same. thread name(): ... end with run name() starts a thread beside the main program.
On a real cellThe controller runs at 500 Hz on the e-Series and 125 Hz on CB3, so one sync() lasts 2 ms or 8 ms. A thread loop that never calls sync() or sleep() never hands that time back.
i = 0
while i < 3:
if get_digital_in(0):
movel(p_pick, a=1.2, v=0.25)
end
i = i + 1
end| Instruction | Category | Purpose | Example |
|---|---|---|---|
| movej | Motion | Move in joint space (linear in joint angles). a in rad/s², v in rad/s; defaults 1.4 and 1.05. A pose target is converted to joint angles by the controller. | movej(p[0.4, 0.0, 0.3, 0, 3.1416, 0], a=1.4, v=1.05) |
| movel | Motion | Move the TCP in a straight line. a in m/s², v in m/s; defaults 1.2 and 0.25 (250 mm/s). | movel(p[0.4, 0.1, 0.3, 0, 3.1416, 0], a=1.2, v=0.25) |
| movep | Motion | Process move: linear segments at constant tool speed, with circular blends of radius r (m) between them. | movep(p[0.4, 0.1, 0.3, 0, 3.1416, 0], a=1.2, v=0.25, r=0.05) |
| movec | Motion | Circular move through a via pose to an end pose. | movec(p_via, p_to, a=1.2, v=0.25, r=0) |
| speedj | Motion | Accelerate to a joint velocity vector (rad/s) and hold it for t seconds. | speedj([0, 0, 0, 0, 0, 0.2], 0.5, 1.0) |
| speedl | Motion | Accelerate to a TCP velocity vector (m/s and rad/s) and hold it for t seconds. | speedl([0.1, 0, 0, 0, 0, 0], 0.25, 1.0) |
| stopj | Motion | Decelerate joint motion to a stop at a in rad/s². | stopj(2.0) |
| stopl | Motion | Decelerate TCP motion to a stop at a in m/s². | stopl(2.0) |
| set_tcp | Frames | Set the TCP pose relative to the tool flange. | set_tcp(p[0, 0, 0.15, 0, 0, 0]) |
| set_target_payload | System | Set payload mass (kg), centre of gravity (m) and inertia matrix. Replaces set_payload from software 5.10. | set_target_payload(1.5, [0, 0, 0.05], [0.002, 0.002, 0.002, 0, 0, 0]) |
| set_payload | System | Older payload call: mass (kg) and centre of gravity (m). Deprecated since software 5.10; still found in older programs. | set_payload(1.5, [0, 0, 0.05]) |
| set_digital_out | I/O | Set standard digital output n (0 to 7) in the control box. | set_digital_out(1, True) |
| get_digital_in | I/O | Read standard digital input n; returns True or False. | if get_digital_in(0): |
| set_tool_digital_out | I/O | Set tool-flange digital output n (0 or 1). | set_tool_digital_out(0, True) |
| get_tool_digital_in | I/O | Read tool-flange digital input n (0 or 1). | if get_tool_digital_in(0): |
| set_analog_out | I/O | Set an analog output as a fraction 0 to 1 of its range: 0.5 is 5 V, or 12 mA in current mode. Newer manuals name it set_standard_analog_out. | set_analog_out(0, 0.5) |
| get_analog_in | I/O | Read an analog input. Deprecated; newer software uses get_standard_analog_in and get_tool_analog_in. | x = get_analog_in(0) |
| rq_open | I/O | Open a Robotiq gripper. Not built into URScript: defined by the Robotiq URCap when it is installed. | rq_open() |
| rq_close | I/O | Close a Robotiq gripper. Also defined by the Robotiq URCap. | rq_close() |
| sleep | Wait | Pause for t seconds. | sleep(0.5) |
| sync | Wait | Use up the rest of the current control cycle: 2 ms on e-Series (500 Hz), 8 ms on CB3 (125 Hz). Put it in wait loops and thread loops. | sync() |
| if | Flow | Branch: if cond: ... elif cond: ... else: ... end. | if get_digital_in(0): |
| elif | Flow | Further condition inside an if block. | elif count > 5: |
| else | Flow | Else branch inside an if block. | else: |
| while | Flow | Loop while a condition is true: while cond: ... end. URScript has no for loop, so counted loops are a while with a counter. | while i < 5: |
| def | Flow | Define a function: def name(args): ... end. | def pick(): |
| end | Flow | Closes if, while, def and thread blocks. | end |
| break | Flow | Leave the innermost loop. | break |
| continue | Flow | Skip to the next pass of the innermost loop. | continue |
| return | Flow | Leave a function, optionally with a value. | return |
| halt | Flow | Stop the program. | halt |
| thread / run | Flow | thread name(): ... end defines a thread; run name() starts it beside the main program. | thrd = run monitor() |
| global | Variable | Declare a variable global, so functions share it. | global count = 0 |
| p[…] | Pose | Pose literal: x, y, z in metres, then rx, ry, rz as an axis-angle rotation vector in radians. | p[0.4, 0.1, 0.3, 0, 3.1416, 0] |
| pose_add | Pose | Add the positions of two poses and compose their rotations, in the base frame. | pose_add(p_base, p[0, 0, 0.05, 0, 0, 0]) |
| pose_inv | Pose | Invert a pose. | pose_inv(p_tool) |
| pose_trans | Pose | Apply p_offset expressed in the frame p_frame: the usual way to move relative to the tool or a feature. | pose_trans(p_pick, p[0, 0, -0.05, 0, 0, 0]) |
| rpy2rotvec | Pose | Convert roll, pitch, yaw (rad) to the rotation vector used in poses. rotvec2rpy converts back. | rv = rpy2rotvec([0, 3.1416, 0]) |
| get_actual_tcp_pose | Pose | The measured TCP pose in the base frame. | p_now = get_actual_tcp_pose() |
| get_actual_joint_positions | Pose | The measured joint angles, six values in radians. | q = get_actual_joint_positions() |
| popup | System | Show a message on the teach pendant: text, title, warning, error, blocking. | popup("Cycle done", "Info", False, False, False) |
| textmsg | System | Write a message to the log. | textmsg("step=", i) |
| powerdown | System | Power down the robot from the program. | powerdown() |
| # | Misc | Comment to the end of the line. | # pick up the part |