Library / SDK
SintefManufacturing/python-urx avatar
SintefManufacturing/python-urx

urx: driving a Universal Robots arm from Python

Python library to control a robot from 'Universal Robots' http://www.universal-robots.com/

614 stars286 forksPythonLGPL-3.0

At a glance

What is it?
urx is a Python library for controlling Universal Robots robot arms through the controller's secondary port and real-time interfaces, offering move commands in joint and Cartesian space, force feedback and gripper support. It is aimed at pick and place rather than high-rate control, and it is published under the LGPL with no guarantee of any kind.
Who is it for?
urx suits a lab or production team doing pick and place with a UR arm and wanting a Python API with a handful of readable calls rather than a vendor toolchain, especially since it can optionally hand over transformation matrices to and from the math3d library.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 141 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two controller interfaces, one Python object

urx is a Python library to control the robots from Universal Robots, published under the LGPL license and coming with absolutely no guarantee, as the README puts it. The architecture choice worth knowing about is that both the secondary port interface and the real-time or matlab interface of the UR controller are used, so the library speaks the controller's native dialects rather than wrapping a single one. The Robot object is constructed with the controller's address, and the methods are named after the movement primitives you would expect from a robot manual. The positioning and the payload are set first, since the tool centre point and the payload weight change how every later move is interpreted:

python
import urx

rob = urx.Robot("192.168.0.100")
rob.set_tcp((0, 0, 0.1, 0, 0, 0))
rob.set_payload(2, (0, 0, 0.1))

The README notes that the package is known to work with all release robots from Universal Robot, and that it was primarily developed by Olivier Roulet-Dubonnet for Sintef Raufoss Manufacturing, which tells you the library grew out of a manufacturing environment rather than a research demo.

Move commands in joint and Cartesian space

The movement API is small and split by the coordinate space you are thinking in. A joint move is movej with six values, and a linear move is movel, also with six, and the acceleration and velocity arguments are passed positionally in the examples. There is also a translate that moves the tool and keeps the orientation, and a movel call with relative=true that moves relative to the current pose:

python
rob.movej((1, 2, 3, 4, 5, 6), a, v)
rob.movel((x, y, z, rx, ry, rz), a, v)
rob.translate((0.1, 0, 0), a, v)  #move tool and keep orientation
rob.stopj(a)

The acceleration and velocity values are left as variables in the README's example, which is honest about a real ergonomic problem with the API: there is no named default you can lean on, so every call site spells out its own motion profile. There are also convenience properties for the common case. rob.x returns the current x, rob.rx returns 0, though the comment notes it could return the x component of an axis vector but that it is not very useful, and you can write to them, so rob.rx -= 0.1 rotates the tool around the X axis and rob.z_t += 0.01 moves the robot along the tool z axis by a centimetre. A new coordinate system can be generated from three points with new_csys_from_xpy and then set with set_csys.

Asynchronous moves and the wait flag

The examples show the pattern for a move that should not block, and the sleep before it is the instructive part. The README's own comment explains it, telling you to sleep first since the robot may not have processed the command yet. The blocking version waits for the move to complete, and the non-blocking version passes wait=False so the program can be sent and then polled:

python
rob.movel(x, y, z, rx, ry, rz), wait=False)
while True :
    sleep(0.1)  #sleep first since the robot may not have processed the command yet
    if rob.is_program_running():
        break

The same shape appears again for a force-triggered move, where the loop watches the force reading and breaks out if the program stops running, and that is the pattern for a guarded approach, such as pushing until a contact force is reached. The exception path is handled with a try block, since a move can fail for reasons outside your control:

python
try:
    rob.movel((0,0,0.1,0,0,0), relative=True)
except RobotError, ex:
    print("Robot could not execute move (emergency stop for example), do something", ex)

The force API is what makes guarded moves possible at all, with getForce and a force threshold loop, and it is the reason the tool is usable for insertion tasks rather than only free-space motion.

Transform matrices through the optional math3d integration

urx can optionally use the python-math3d library, which is GPL licensed, to receive and send transformation matrices to the robot. The development example shows the full round trip. You build a Transform for the tool centre point, set a position and an orientation on it, and hand it to set_tcp, then read the current pose back as a transformation matrix from tool to base, mutate it, and apply it again:

python
from urx import Robot
import math3d as m3d

robot = Robot("192.168.1.1")
mytcp = m3d.Transform()  # create a matrix for our tool tcp
mytcp.pos.z = 0.18
mytcp.orient.rotate_zb(pi/3)
robot.set_tcp(mytcp)
time.sleep(0.2)

# get current pose, transform it and move robot to new pose
trans = robot.get_pose()  # get current transformation matrix (tool to base)
trans.pos.z += 0.3
trans.orient.rotate_yb(pi/2)
robot.set_pose(trans, acc=0.5, vel=0.2)  # apply the new pose

There is also a narrower path that touches only the orientation, with get_orientation and set_orientation. The value of this route is that pose maths happens in a library with proper rotation algebra rather than in six loose numbers you have to keep in sync, and the README points out that you can work with only the orientation part when that is all you need. The cost is a dependency with a different licence, GPL, sitting inside an LGPL library, which is the kind of detail worth checking against your own distribution obligations.

A gripper class for the Robotiq two finger

The library also drives a Robotiq gripper attached to the UR robot, through a class that was primarily developed by Mark Silliman. The example shows the shape of the integration, where the gripper object is constructed, the robot is created, and the command is chosen from the command line arguments:

python
import sys
import urx
from urx.robotiq_two_finger_gripper import Robotiq_Two_Finger_Gripper

if __name__ == '__main__':
	rob = urx.Robot("192.168.0.100")
	robotiqgrip = Robotiq_Two_Finger_Gripper()

	if(len(sys.argv) != 2):
		print "false"
		sys.exit()

	if(sys.argv[1] == "close") :
		robotiqgrip.close_gripper()
	if(sys.argv[1] == "open") :
		robotiqgrip.open_gripper()

The mechanism is worth noting because it is not a direct serial call. The gripper object builds a URScript program and hands it to the robot with send_program, so the gripper action is executed by the controller as a robot program rather than by the Python process talking to the gripper. The example then closes the robot connection with rob.close() at the end. That design keeps the timing on the controller's side, which is what you want for a device that has to be synchronised with the arm's motion, and it also means the gripper inherits the robot's own error handling.

Where this library stops being the right tool

The limits are stated in the README and one of them is decisive. The library is meant as an easy to use module for pick and place operations, and although it has been used for welding and other sensor based applications, those do not require high control frequency, which is the library's explicit exclusion. A closed-loop force or position control task running at kilohertz rates needs the real-time interface and a different programming model entirely. The second limit is the Python version, and it is visible in the documentation rather than hidden. The examples use the Python 2 print statement form, the except RobotError, ex syntax that Python 3 replaced, and a movel call written as rob.movel(x, y, z, rx, ry, rz), wait=False) with a comma instead of a parenthesis. The setup script declares Python 3 support in its classifiers, so the package installs and imports on Python 3, but the code in the documentation is not Python 3 and will not run as written. The third limit is the absence of a release history. The repository publishes no GitHub releases, the setup script carries version 0.11.0, and the classifier says Development Status 4 - Beta, so you are depending on a snapshot of a beta rather than on a versioned series with a changelog.

Licence, packaging and the state of the repository

The licence is LGPL-3.0, and the README is direct about what that comes with, saying the library comes with absolutely no guarantee. The setup file declares the GNU Lesser General Public License v3 and lists numpy and math3d at version 3.3 as the install requirements, so the math3d dependency is pinned rather than floating, which matters if you also use math3d for something else. The repository carries a debian/ directory and a make_deb.py script, so a Debian package is produced, and there is a make_windows_installer.bat alongside a release.py, which shows the project has been packaged for both Linux and Windows. The examples directory is broader than the README suggests, with get_realtime_data.py, joystick_control.py, matrices.py, simple.py, spnav_control.py, test_all.py and test_movep.py among the files, and the spnav example points to a 3Dconnexion SpaceNavigator device being used to drive the arm interactively. The last push to the repository was on 2026-06-04, and the project was primarily developed for Sintef Raufoss Manufacturing, which is the kind of provenance that explains both the LGPL choice and the manufacturing-focused API.

Editorial conclusion

urx suits a lab or production team doing pick and place with a UR arm and wanting a Python API with a handful of readable calls rather than a vendor toolchain, especially since it can optionally hand over transformation matrices to and from the math3d library. It is the wrong tool for applications that need high control frequency, because the README says so explicitly, and it is the wrong tool if you are on Python 3, since the examples in the documentation use Python 2 syntax such as print statements and the except RobotError, ex form, so the documented code will not run as written on a modern interpreter. Verify first which Python version your code actually needs, check that math3d is available since the setup script pins it at a specific version, and note that the repository has no GitHub releases while the last push was on 2026-06-04.

Frequently asked questions

How do I install urx?

The README says the easiest method is pip install urx. The setup script pins the install requirements to numpy and math3d at version 3.3.

What can urx control besides the robot arm itself?

It can also control a Robotiq gripper attached to the UR robot, through the Robotiq_Two_Finger_Gripper class, which builds a program that is sent to the robot with send_program. The class was primarily developed by Mark Silliman.

Does urx work with transformation matrices?

Yes, optionally through the python-math3d library, which is GPL licensed. You can set the tool centre point from a math3d Transform, read the current pose with get_pose, mutate it, and apply it again with set_pose, and there is a narrower path using get_orientation and set_orientation.

Is urx suitable for high control frequency applications?

No. The README says it is meant as an easy to use module for pick and place operations, and although it has been used for welding and other sensor based applications, those do not require high control frequency.

Which Python version do the urx examples use?

The examples in the README are written in Python 2 style, with print statements and the except RobotError, ex form, even though the setup script declares Python 3 support in its classifiers. The documented code will not run as written on a modern interpreter.

Official sources

  1. Issues
  2. License: LGPL-3.0
  3. README
  4. SintefManufacturing/python-urx on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sintefmanufacturing-python-urx.svg)](https://hysenlabs.com/projects/sintefmanufacturing-python-urx)

Was this useful?