Building a Custom Gazebo Model Plugin to Apply Constant Force
Learn how to create a Gazebo model plugin that applies a constant force, with build instructions, loading steps, and a discussion of version‑locking and stability trade‑offs.
23 Jul 2025, 19:37 UTC

Problem: You need an actuator behavior that Gazebo’s built‑in plugins don’t provide
Suppose you are simulating a robot with a proprietary linear actuator whose force‑velocity curve is not captured by any of Gazebo’s standard joint controllers. You could try to approximate it with a PID controller, but the approximation introduces latency and may hide subtle dynamics you actually want to study. The straightforward way to inject the exact force you need is to write a Gazebo ModelPlugin that runs inside the simulator’s process and applies the force directly to a link or joint each simulation step.
Thesis: A Gazebo model plugin gives you low‑latency, direct access to the physics engine while keeping the code simple enough for rapid iteration
Because the plugin is loaded as a shared library into Gazebo’s address space, there is no ROS middleware hop, no message serialization delay, and you can read or write any internal state that the physics engine exposes. The trade‑off is that any bug in the plugin can crash the whole simulator, and you must compile against the exact Gazebo version and ABI (Application Binary Interface).
Understanding the plugin interface
Every model plugin must inherit from gazebo::ModelPlugin and implement at least two methods:
Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)– called once when the plugin is instantiated. Here you store a pointer to the model, parse SDF parameters, and optionally connect to the world update event.Update()– called every simulation step (by default via theConnectWorldUpdateBegincallback). This is where you apply forces, read sensor values, or modify joint states.
The plugin receives a physics::ModelPtr that gives you access to links, joints, and the underlying physics engine (gazebo::physics::PhysicsEnginePtr). Because the plugin runs in the same process, the latency between deciding a force and seeing its effect is essentially the simulation step time.
Worked example: a plugin that applies a constant force along the X‑axis
The following minimal C++ source shows the essential steps. Replace YOUR_LINK_NAME with the link you want to actuate, and adjust the force magnitude as needed.
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
namespace gazebo {
class ConstantForcePlugin : public ModelPlugin {
public:
void Load(physics::ModelPtr _model, sdf::ElementPtr /*_sdf*/) override {
this->model = _model;
this->link = model->GetLink("YOUR_LINK_NAME");
if (!this->link) {
gzerr << "ConstantForcePlugin: link not found\n";
return;
}
// Connect to the world update begin event
this->updateConnection = event::Events::ConnectWorldUpdateBegin(
std::bind(&ConstantForcePlugin::OnUpdate, this));
}
private:
void OnUpdate() {
// Apply a constant 10 N force along the link’s local X axis
this->link->AddForce(ignition::math::Vector3d(10.0, 0.0, 0.0));
}
physics::ModelPtr model;
physics::LinkPtr link;
event::ConnectionPtr updateConnection;
};
// Register this plugin with the simulator
GZ_REGISTER_MODEL_PLUGIN(ConstantForcePlugin)
}
To build the plugin against Gazebo 11 (or Gazebo Classic), run the following command on your development machine. This requires g++ and pkg-config to be installed:
g++ -std=c++11 -fPIC -shared constant_force.cpp \\
-o libconstant_force.so `pkg-config --cflags --libs gazebo`
The command assumes pkg-config can locate Gazebo’s headers and libraries. If you are using a Gazebo installation in a non‑standard prefix, adjust PKG_CONFIG_PATH accordingly.
Loading the plugin in a world
Create or edit a world file (e.g., constant_force.world) and add a <plugin> tag inside the model you want to affect:
<model name="my_robot">
<!-- existing links, joints, etc. -->
<plugin filename="libconstant_force.so" name="constant_force_plugin" />
</model>
Launch Gazebo with:
gazebo constant_force.world
You should see no error messages in the console, and the link named YOUR_LINK_NAME will experience a steady 10 N force in its local X direction, causing it to accelerate according to the simulated mass and inertia.
Trade‑offs and limitations
- Version lock: The plugin must be compiled against the exact Gazebo major/minor version and ABI. A mismatch leads to a load‑time error (
Failed to load plugin libconstant_force.so) or, worse, a silent crash. - Stability risk: Because the plugin shares the simulator’s address space, an infinite loop, memory corruption, or dereferencing a null pointer can bring down the entire Gazebo process. Always test new plugins in an isolated world first.
- Debugging: Standard
gdbattaches to the Gazebo process, but you may need to disable real‑time factors (-uflag) to step throughOnUpdatecomfortably.
Practical verification steps
- Compile the plugin as shown above and verify that
libconstant_force.soappears without linker errors. - Start Gazebo with the world file and observe the console for any
gzerrmessages. - Enable Gazebo’s
--verboseflag to see physics step timing; ensure the simulation runs at a stable real‑time factor. - Optionally, add a simple
gzdbgstatement insideOnUpdateto confirm the callback is being invoked each step.
If the link moves as expected, the plugin is working. If the simulation crashes, check the plugin’s source for null‑pointer accesses or excessive computation that could stall the scheduler.
Actionable closing
Writing a Gazebo model plugin is the most direct way to inject custom forces, torques, or sensor noise when built‑in plugins fall short. Start with a minimal example like the constant‑force plugin above, compile against your exact Gazebo version, and run it in a sandbox world before integrating it into a larger simulation. If you later need ROS 2 integration, consider wrapping the plugin in a thin ROS 2 node that communicates via topics, keeping the low‑latency core inside Gazebo while isolating the rest of your software stack.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.