gz-sim icon indicating copy to clipboard operation
gz-sim copied to clipboard

Scripting: plugins that aren't written in C++

Open chapulina opened this issue 3 years ago • 5 comments

Desired behavior

In the interest of making Gazebo more approachable and quicker to use, we could offer a scripting interface that lets users write plugins in Python (and potentially other languages too, like Ruby and Javascript).

The user could load their Python script through the SDF's <plugin> tag, just like they do with C++ libraries right now.

Alternatives considered

Users could use ROS to interact with Gazebo using various scripting languages. We could also just offer a scripting API to Ignition Transport, so users can interact with simulation asynchronously.

These 2 approaches limit what the user can do though, because they're constrained to the transport API and must run async.

Implementation suggestion

First we'll need to expose C++ APIs that are commonly used by plugins to scripts, such as:

  • The EntityComponentManager's API so users can access and modify entities and components
  • Component data types so that they can be read and manipulated. Components commonly include Math, SDFormat and Msgs types.
  • All the System callbacks need to be inherited by the Python plugin so that they're called at the correct time during simulation.
    • Note that in C++, systems receive a mutable reference to the ECM through these callbacks, and that's how they modify components and entities. This may be complicated to do when interfacing with Python.

We can either add C APIs to all of these and then call those C functions from scripts, or use SWIG to generate APIs in other languages from C++.

Finally, we'll need to support loading those scripts from the <plugin> tag.

Additional context

Related to https://github.com/ignitionrobotics/ign-gazebo/issues/789

chapulina avatar Apr 26 '21 20:04 chapulina

The EntityComponentManager's API so users can access and modify entities and components

An alternative idea is to only provide python bindings for the simpler wrapper classes, like World, Model and Link, see https://github.com/ignitionrobotics/ign-gazebo/issues/325. The rationale is:

  • The scripting interface is geared towards beginners, so we should expose our simpler APIs. Advanced users who'd directly use the ECM should be able to, and most likely will choose to, use C++.
  • I think it may be simpler to expose those classes and their simpler setters and getters, than to expose the ECM and all the complexity that comes with templates, component creation, etc.

chapulina avatar Aug 10 '21 16:08 chapulina

A possible implementation of plugins that can be developed in Python is exploiting the functional features of pybind11. I've never tried to do it myself, but it is maybe possible to develop toolings comparable to https://github.com/ignitionrobotics/ign-gazebo/pull/926 to define a system with custom-defined callbacks implemented in Python

Example
class MyPythonSystem:
    def __init__(server: gazebo.Server):

        # Created from the ECM in the configuring phase
        self.model: Optional[gazebo.Model] = None
        
        # Custom system that allows registering callbacks
        helper_system = gazebo.HelperSystem()
        helper_system.configure_callback = functools.partial(MyPythonSystem.on_configure, self=self)
        helper_system.pre_update_callback = functools.partial(MyPythonSystem.on_pre_update, self=self)

        # Insert the system programmatically
        if not server.add_system(system=helper_system, world_idx=0):
            raise RuntimeError("Failed to insert helper system")

    def on_configure(
        self,
        entity: gazebo.Entity,
        sdf_element: sdf.Element,
        ecm: gazebo.EntityComponentManager,
        event_manager: gazebo.EventManager) -> None:

        # Create and store the model
        self.model = gazebo.Model(entity)
        assert self.model.valid()

    def on_pre_update(
        self,
        update_info: gazebo.UpdateInfo, 
        ecm: gazebo.EntityComponentManager) -> None:

        print(self.model.name())
        # TODO other logic calling Model methods


server_config = ServerConfig()
server_config.set_sdf_file(sdf_file='rolling_shapes.sdf')
server = Server(server_config=server_config)

my_system = MyPythonSystem(server=server)
assert server.run(blocking=True, iterations=1, paused=True)

# Now running the server should also run MyPythonSystem.on_pre_update
assert server.run(blocking=True, iterations=1, paused=False)

# Calling methods from here should also be possible, exposing post-update ECM data
print(my_system.model.name())

diegoferigo avatar Oct 11 '21 09:10 diegoferigo

This issue has been mentioned on Gazebo Community. There might be relevant details there:

https://community.gazebosim.org/t/ignition-python-wrappers-released-in-fortress/1332/3

osrf-triage avatar Mar 14 '22 23:03 osrf-triage

This is so perfect!

I'm so thrilled to see this! I have a quick question, would this be on Fortress only or would it include Citadel too? 👼

Kakcalu13 avatar Mar 28 '22 14:03 Kakcalu13

Hi @Kakcalu13 , we currently have no plans to work on this in the coming months. This ticket is here to discuss possible approaches and gather feedback from the community. Glad to know there's interest! We also accept contributions 😄

chapulina avatar Mar 28 '22 16:03 chapulina