{"id":27913725,"url":"https://github.com/aroefer/prime_bullet","last_synced_at":"2025-05-06T14:43:24.150Z","repository":{"id":117491072,"uuid":"137282929","full_name":"ARoefer/prime_bullet","owner":"ARoefer","description":"This repository contains a Python wrapper for the PyBullet API, to easily simulate robots and their environments.","archived":false,"fork":false,"pushed_at":"2024-05-06T16:01:15.000Z","size":6899,"stargazers_count":3,"open_issues_count":7,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-06T19:55:44.057Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ARoefer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-06-13T23:39:57.000Z","updated_at":"2024-11-12T14:35:42.000Z","dependencies_parsed_at":"2024-04-04T17:09:44.172Z","dependency_job_id":null,"html_url":"https://github.com/ARoefer/prime_bullet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ARoefer%2Fprime_bullet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ARoefer%2Fprime_bullet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ARoefer%2Fprime_bullet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ARoefer%2Fprime_bullet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ARoefer","download_url":"https://codeload.github.com/ARoefer/prime_bullet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252706007,"owners_count":21791301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-06T14:43:22.323Z","updated_at":"2025-05-06T14:43:24.141Z","avatar_url":"https://github.com/ARoefer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prime Bullet\n\nPrime Bullet is an object-oriented wrapper for PyBullet which tries to offer a game-engine like interaction with the physics simulator. Aside from the structuring of data, it also include a small SE3/SO3 math library compatible with bullet and numpy, implementations of cameras and laser scanner, as well as simple controllers for robot. Its primary audience are roboticists who are researching manipulation -- both mobile and static.\n\n\n## Installation\n\nInstalling *prime-bullet* is easy. Simply clone the repository, go to its root and run `pip install -e .`:\n\n```bash\ngit clone https://github.com/ARoefer/prime_bullet\n\ncd prime_bullet\n\npip install -e .\n```\n\nFor the usage with ROS *noetic*, we recommend [rosvenv](https://github.com/ARoefer/rosvenv) as a companion tool.\n\n\n## Getting Started\n\nA brief overview to get you into the features of *prime bullet*.\n\n### The Simulator\n\nPrime Bullet uses a simulator instance as interface to bullet. In case you are familiar with the inner workings of bullet: This instance maintains the connection to the bullet server.\n\n```python\nimport prime_bullet as pb\n\n# Simulator with 50Hz rate, standard gravity, and no EGL rendering\nsim = pb.Simulator()\n\n# Establishes connection with Bullet in 'gui' mode. There is also headless, aka 'direct' mode\nsim.init('gui')\n\n# Second simulator with 100Hz rate.\nsim2 = pb.Simulator(100)\n# New connection to bullet. This simulator is completely separate from the first\nsim2.init('direct')\n```\n\nThe typical life-cycle for the simulator looks like this:\n\n```python\nimport prime_bullet as pb\n\nsim = pb.Simulator()\nsim.init('gui')\n\n# Create a couple objects\n# ....\n\n# Simulate\nwhile not some_stopping_condition:\n    # Preforms the actual physics step\n    sim.update()\n    # Maybe do something with the result\n\n    # Maybe reset the sim after a while, if you are doing RL or something similar:\n    if episode_ended:\n        sim.reset()\n\n# Actually only needed when using plugins -\u003e We'll talk about them later.\nsim.stop()\n# Closes the connection to bullet\nsim.kill()\n```\n\n### Objects\n\nCreation of objects is handled through the simulator, as objects are always associated with exactly one simulation. The following object types can be created:\n\n```python\nimport prime_bullet as pb\n\nsim = pb.Simulator()\nsim.init('gui')\n\n# Create a couple objects\n# A red box with 20cm edges at (0, 1, 0.2)\nbox      = sim.create_box([0.2] * 3, pb.Transform.from_xyz(0, 1, 0.2))\n\n# A blue sphere with 30cm radius at (0, 2, 0.6)\nsphere   = sim.create_sphere(0.3, pb.Transform.from_xyz(0, 2, 0.6))\n\n# A 1m tall green cylinder with a radius of 20cm at (0, 3, 1)\ncylinder = sim.create_cylinder(0.2, 1, pb.Transform.from_xyz(0, 3, 1))\n\n# A monkey mesh at (0, 4, 1)\nmesh     = sim.create_mesh('package://iai_bullet_sim/meshes/suzanne.dae', pose=pb.Transform.from_xyz(0, 4, 1))\n\n# A URDF of a windmill at (0, 5, 0)\nwindmill = sim.load_urdf('package://iai_bullet_sim/src/iai_bullet_sim/data/urdf/windmill.urdf', pb.Transform.from_xyz(0, 5, 0))\n\n# Simulate\nwhile not some_stopping_condition:\n    # Preforms the actual physics step\n    sim.update()\n\n    # Print the pose of the box after every simulation step\n    print(f'Box pose: {box.pose}')\n\n```\n\nThere are two types of objects in prime bullet: Rigid bodies and articulated objects. The latter are an extension of the former, which we will talk about in more detail in a second.\nRigid bodies have a number of convenience functions, but primarily it is important to point out the following:\n\n```python\nimport prime_bullet as pb\n\nsim = pb.Simulator()\nsim.init('gui')\n\nbox = sim.create_box([0.2] * 3, pb.Transform.from_xyz(0, 1, 0.2))\n\n# Current pose\na = box.pose  \n# Set a new pose\na.pose = pb.Transform.from_xyz(1, 2, 3)\n\n# Initial pose\na = box.initial_pose\n# Set new initial pose, does not move the object\nbox.initial_pose = pb.Transform.from_xyz(1, 2, 3)\n\n# Object is reset to initial pose\nbox.reset()\n\n# Get the current linear velocity\nvl = box.linear_velocity\n\n# Get the current angular velocity\nva = box.linear_velocity\n\n# Get the current AABB of the object\naabb = box.aabb\n\n```\n\nArticulated objects, such as robots, are more complicated, as they contain joints and their states. Notable functions are:\n\n```python\nimport prime_bullet as pb\n\nsim = pb.Simulator()\nsim.init('gui')\n\nmill = sim.load_urdf('package://iai_bullet_sim/src/iai_bullet_sim/data/urdf/windmill.urdf', pb.Transform.from_xyz(0, 5, 0))\n\n# Links\nl = mill.i_links[1]    # Get a link by index\nl = mill.links['head'] # Get a link by its name\nl.name  # Name of the link\nl.pose  # World pose of the link\nl.state # Complete state of the link\nl.state.linear_velocity   # Current linear velocity\nl.state.angular_velocity  # Current angular velocity\nl.aabb  # Current AABB of the link\nl.jacobian(q, q_dot, q_ddot, point) # Calculate Jacobian for point in frame of the link, given the current q, qd, and desired qdd\nl.ik(world_pose, max_iterations)    # Calculate IK for a desired object position or pose. Returns q as np.array\n\n# Joints\n# List of all joint names, in order\njoints  = mill.joint_names\n# List of names of dynamic joints, aka non-static ones, in order\ndjoints = mill.dynamic_joint_names\n\njoint   = mill.joints['some_joint'] # Get a joint by its name\njoint   = mill.i_joints[2]          # Get a joint by its index\n# Some of the attributes of a joint (there are many more)\njoint.limits # The joint's limits\njoint.f_max  # The joint's maximum torque/effort\njoint.qd_max # The joint's maximum velocity\njoint.link   # The child link of this joint\njoint.parent # The parent link of this joint\njoint.is_dynamic # Indicates whether the joint is fixed or dynamic\n\n# Current dynamic joint data as np.array, same order as djoints\nq  = mill.q     # Positions\nqd = mill.q_dot # Velocities\nqf = mill.q_f   # Torques\n\n# Current structured dynamic joint information as dictionary\njs = mill.joint_state\n\n# Set current joint positions from a dictionary\nmill.set_joint_positions(js, override_initial=False)\n\n# Set current dynamic joint positions from a np.array and override joint pose the object is reset to\nmill.set_joint_positions(q, override_initial=True)\n\n# Resets both root pose and joint pose\nmill.reset()\n\n# Controlling the joints\n# Setting a joint position goal as dict or np.array with max forces (np.array)\nmill.apply_joint_pos_cmds(cmd, max_force)\n# Setting a joint velocity goal as dict or np.array with max forces (np.array)\nmill.apply_joint_vel_cmds(cmd, max_force)\n# Setting a joint torque goal as dict or np.array\nmill.apply_joint_torque_cmds(cmd)\n\n# Returns Force-Torque sensor for the given joint. Joint can also be fixed\nft_sensor = mill.get_ft_sensor('some_joint')\nwrench = ft_sensor.get()  # Current wrench measurement\n```\n\n### Search Paths\nAs you could see before, prime bullet is able to use *package* paths. In case you are working with ROS, prime bullet will automatically add your ROS package path to the paths it searches. In case you are using prime bullet without ROS, you can always add paths manually with `add_search_path()`:\n\n```python\nimport prime_bullet as pb\n\npb.add_search_path('my_awesome_library_root')\npb.add_search_path('some_other_place/my_package')\n\n# Will be resolved successfully if bla is a directory contained in one of the dirs in the search path\npath_a = pb.res_package_path('package://bla/some_file.txt')\n\n# Will be resolved because 'my_package' is part of the search paths.\npath_b = pb.res_package_path('package://my_package/some_file.txt')\n```\n\nAs pybullet is limited to a single search path, prime bullet re-writes all loaded URDF files to use global paths. It is important, that all the packages mentioned in the URDF can be found over the search paths.\n\n### Spatial Transformations\nThroughout the examples, we have encountered `pb.Transform` multiple times. This class is part of prime bullet's micro spatial transformation library, which we will introduce you to briefly.\n\nThe module implements only 4 datatypes: `Vector3`, `Point3`, `Quaternion`, and `Transform`. The first three are extensions of Python's `tuple` type and are thus compatible with both `pybullet` and `numpy` without any need for conversions. However, when combined with one another they do follow the rules of SE3 algebra. Let us look at a couple examples:\n\n```python\nimport numpy as np\nfrom prime_bullet import Vector3, Point3, Quaternion, Transform\n\np1 = Point3(2, 0, 0)\nv1 = Vector3(0, 1, 0)\n\nq1 = Quaternion.from_axis_angle(Vector3.unit_z(), np.deg2rad(90))\nq2 = Quaternion.from_rpy(np.deg2rad(90), 0, 0)\nt1 = Transform(q, Point3(0, 0, 3))\n\n# Points and Vectors\np2 = p1 + v1  # Point +/- Vector -\u003e Point\n# This is the only difference in combining points and vectors. \n# As points are an extension of vectors, they behave the same in all other cases\nv2 = p1 - p2  # Point - Point -\u003e Vector\np3 = p1 + p2  # Technically nonsensical, but we are merciful and say -\u003e Point\n\nv1 * v2  # Component-wise multiplication\nv1 / v2  # Component-wise division\n\nv1.norm()     # L2 norm of vector/point\nv1.dot(v2)    # Dot-product of vectors/points\nv1.cross(v2)  # Cross-product of vectors/points\nv1.numpy()    # Representation as numpy array\n\nv1.x,  v1.y,  v1.z  # Semantic accessing of elements\nv1[0], v1[1], v1[2] # Indexed accessing of elements\n\n# Quaternions\nq3 = q1.dot(q2)       # Combined rotation of roll and yaw\na  = q1.angle()       # Angle of q1\nad = q1.angle(q2)     # Angle between q1 and q2\nq1.inv()              # Inversion of q1\nqd = q1.inv().dot(q2) # Delta rotation from q1 to q2\nq1.matrix()           # 3x3 Rotation matrix of q1\nq1.numpy()            # Quaternion as (4,) np.array\nq1.lerp(q2, 0.5)      # 50% interpolation from q1 to q2\n\np4 = q1.dot(p3)       # Rotate point p3 around origin by q1\nv3 = q1.dot(v2)       # Rotate vector v2 around origin by q1\n\nt1.position   # t1's translation -\u003e point\nt1.quaternion # t1's orientation -\u003e quaternion\nt1.inv()      # Inversion of transform t1\nt2 = Transform.from_xyz_rpy(2, 3, 4, \n                            0, np.deg2rad(45), 0)\nt1.matrix()           # 4x4 Homogenous transformation matrix\nt3 = t1.dot(t2)       # t2 transformed by t1\np5 = t1.dot(p1)       # p1 rotated 90 deg around t1's z-axis and translated by (0, 0, 3)\nv4 = t1.dot(v1)       # v1 rotated 90 deg around z-axis\nq4 = t1.dot(q3)       # q3 rotated by the rotation of t1\ntd = t1.relative(t2)  # Relative transform between t1 and t2\nt4 = t1.lerp(t2, 0.5) # 50% transformation between t1 and t2\n```\n\nThere are more constructors for all these data types and more operators. It is worthwhile to check the class reference for these types/the `geometry.py` file.\n\n### Frames\nPrime bullet adds the concept of frames, which allow users to build transformation hierarchies. A frame always has a `local_pose` and a `pose`, which describe their transformation either relative to their parent, or relative to the world. All bodies and all links implement the frame concept, however here both of these attributes are always identical, as pybullet does not support the concept of frames. Nonetheless, frames become useful when we want to attach \"virtual\" object, such as cameras or other sensors, to the bodies managed by the physics simulation. As an example, let us take a look at the `PerspectiveCamera` class.\n\n```python\nimport prime_bullet as pb\n\nlink = some_body.links['link']\n\n# TODO: What's the uint of FOV?\ncam  = pb.PerspectiveCamera(sim, (200, 200), 70, 0.1, 10.0, pb.Transform.from_xyz(0.1, 0, 0), parent=link)\n\n# Behind the scenes, the current camera pose is determined before an image is generated.\n# Thus the camera is now rigidly attached to 'link'\nrgb  = cam.rgb()\n```\n\n### Additional Sensors\nWe don't have the time to go into details, but we would like to point out that prime bullet provides additional sensors. As we have seen in the previous section, there is a `PerspectiveCamera`, but there is also a laser scanner:\n\n\n```python\nimport prime_bullet as pb\n\n# Camera rendering 200x200 pixel images at 70deg FOV in a volume from 10cm-10m\ncam  = pb.PerspectiveCamera(sim, (200, 200), 70, 0.1, 10.0)\nrgb        = cam.rgb()   # RGB image\ndepth      = cam.depth() # Single channel depth image\nrgb, depth = cam.rgbd()  # Both modalities at once\nseg        = cam.segmentation()  # Pybullet's segmentation mask\n\n# Laser scanner with 180deg coverage, 1000 rays, measuring from 50cm to 10m\nlscanner  = pb.LaserScanner(sim, np.deg2rad(-90), np.deg2rad(90), 1000, 0.5, 10.0, pb.Transform.identity())\ndistances = lscanner.render() # Distances measured by the rays\n```\n\nFor more details, please check the class references. It would also be nice to have a 3d lidar and an orthonormal camera (*hint, hint*).\n\n### Controllers\nIn addition to new sensor modalities, prime bullet also provides a couple controllers to get you started with (Cartesian) robotic control. The following is not going into detail on their usage, but rather serves as an overview.\n\n```python\nimport prime_bullet as pb\n\n# Control for joint positions. Holds current position by default\njc = pb.JointPositionControl(robot)\njc.reset()  # Assume current robot pose as target\njc.delta    # joint space delta\njc.goal     # Current q target\njc.act(q)   # Set a new q target\n\n# Control for Cartesian pose of link. Assume current pose by default\ncc = pb.CartesianController(robot, link)  \ncc.reset()  # Assume current link pose as target\ncc.delta    # 2d array of translational and angular error\ncc.goal     # Current x target\ncc.act(x)   # Set a new x target\n\n# Control for Cartesian pose of link with relative actions. Assume current pose by default\ncc = pb.CartesianRelativeController(robot, link)  \ncc.reset()  # Assume current link pose as target\ncc.delta    # 2d array of translational and angular error\ncc.goal     # Current x target\ncc.act(xd)  # Apply xd transform to current target\n\n# Same as CartesianRelativeController, but without orientation goal\npb.CartesianRelativePointController\n\n# Same as CartesianRelativePointController, but holding a fixed orientation\npb.CartesianRelativePointCOrientationController\n\n# Same as CartesianRelativeController, but a virtual point is moved, unrelated to the link's pose\npb.CartesianRelativeVirtualPointController\n\n# Same as CartesianRelativeVirtualPointController, but while holding a fixed orientation\npb.CartesianRelativeVPointCOrientationController\n```\n\n### Other Things\nThere are three more things in this library to mention:\n \n 1. The `pb.ColorRGBA` type, which behaves like the other types, but represents colors and defines a few constant ones.\n 2. The `pb.AABB` type which represents axis aligned bounding boxes and can be used to check if points are within such a box.\n 3. The `pb.DebugVisualizer` which can be obtained from a `Simulator` instance which is currently in `gui` mode. This class can be used to control pybullet's visualizer.\n\n\n### Plugins\nTODO. But generally, they allow users to attach additional functionality to the `update()` of the simulator. Check the `SimulatorPlugin` class for details.\n\n## Usage with `gym.Env`\n\nBriefly, let us remark on the usage of prime bullet with the OpenAI `gym` API. Since the `Simulator` class seems to have a functional overlap with a `gym.Env`, it might be tempting to create a custom environment by deriving a class from both `Simulator` and `gym.Env`. We advise against this, as it will create a messy architecture in which different semantic goals and timesteps collide. Instead, we propose the following as a rough skeleton for using prime bullet with gym environments:\n\n```python\nimport gym\nimport prime_bullet as pb\n\nclass MyEnv(gym.Env):\n    def __init__(self, hz_action, substeps=1, **more_params_that_I_need):\n        # Create simulation with higher resolution than actual agent frequency\n        self.sim = pb.Simulator(hz_action * substeps)\n        self.sim.init('direct')\n        self._substeps = substeps\n\n        # Create all your objects\n\n    def reset(self):\n        # Do whatever else you need to do\n        self.sim.reset()\n\n        # Custom reset behavior such as priming PID-gains\n        return self.observation()\n\n    def step(self, action):\n        # Post-process your action\n        # Send to simulator\n        \n        # Actual physics. Higher resolution than agent\n        for _ in range(self._substeps):\n            self.sim.update()\n\n        # Calculate your rewards and done flag\n        return self.observation(), reward, done, some_info_dictionary\n    \n    def observation(self):\n        # Do stuff\n        return some_observation_that_you_constructed_from_the_sim\n\n    def close(self):\n        self.sim.kill()\n\n    @property\n    def observation_space(self):\n        return some_specification_of_an_observation_space\n\n    @property\n    def action_space(self):\n        return some_specification_of_an_action_space\n```\n\nAs you can see in the code example, we suggest that you perform multiple simulation steps per agent step. While this is dependent on your agent's action frequency, we have found that a 30Hz simulation frequency easily leads to oscillations in the simulation. \n\n## Conclusion\n\nWe hope this is enough of an overview to get you started. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faroefer%2Fprime_bullet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faroefer%2Fprime_bullet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faroefer%2Fprime_bullet/lists"}