Simulation Extensions

By using OpenRocket’s extension and listener mechanism, it’s possible to modify the program itself to add features that are not supported by it as distributed. Some extensions that have been created already provide the ability to air-start a rocket, to add active roll control, and to calculate and save extra flight data. It’s also not uncommon to see proposed new featues created as extensions first, and then integrated into the program.

This page will discuss simulation extensions, including extensions provided with OpenRocket and third party extensions, and the process of creating a new extension.

Warning

Writing an extension inserts new code into the program. It is entirely possible to disrupt a simulation in a way that invalidates simulation results, or can even crash the program. Be careful!


There are three sources for simulation extensions: extensions that are provided with OpenRocket for use as examples, extensions obtained from third party developers, and extensions you write yourself. The following sections describe these simulation sources.


Example Simulation Extensions Provided With OpenRocket

OpenRocket ships with several example extensions in the source tree. These are available “out of the box”, and you can simply add them following the instructions in section Adding an Extension to a Simulation

The example extensions are all located in core/src/main/java/info/openrocket/core/simulation/extension/example/ and their configurators are all located in swing/src/main/java/info/openrocket/swing/simulation/extension/example/.

The extensions provided with OpenRocket include:

Purpose

Extension

Configurator

Set air-start altitude and velocity

AirStart.java

AirStartConfigurator.java

Save some simulation values as a CSV file

CSVSave.java

(none)

Calculate damping moment coefficient after every simulation step

DampingMoment.java

(none)

Print summary of simulation progress after each step

PrintSimulation.java

(none)

Active roll control

RollControl.java

RollControlConfigurator.java

Stop simulation at specified time or number of steps

StopSimulation

StopSimulationConfigurator

Note that these extensions exist primarily to demonstrate the process. Several of them actually duplicate functionality that is in OpenRocket itself


Extensions Written by Third Party Developers

Several external developers have created extensions, which are distributed as .jar files. If you obtain one of these, you can make it available to your simulation by putting it in a system-dependent plugins directory. The location of this directory is:

Default Plugins Directories by Operating System

Operating System

Plugins Directory

Windows

%APPDATA%\OpenRocket\Plugins (if APPDATA is available)*
C:\Users\[YOUR USERNAME]\OpenRocket\Plugins (fallback if APPDATA is not available)

* APPDATA is usually C:\Users\[YOUR USERNAME]\AppData\Roaming

macOS

/Users/[YOUR USERNAME]/Library/Application Support/OpenRocket/Plugins/

Linux

/home/[YOUR USERNAME]/.openrocket/Plugins/ (note .openrocket is a hidden directory)

[YOUR USERNAME] is your user name on your device.

Once you have placed the plugin in the plugins directory, you can add it to your simulation using the instructions in section Adding an Extension to a Simulation

Sorry, we don’t maintain any sort of directory or library of third party extensions, nor can we vouch for the quality of any extensions provided by third party developers.


Creating a New OpenRocket Extension

Finally, you can create your own extension. It is assumed that you have experience in Java development before reading this section.

Preliminary Concepts

Before we can discuss writing an extension, we need to briefly discuss some of the internals of OpenRocket. In particular, we need to talk about the simulation status, flight data, and simulation listeners.

Simulation Status

As a simulation proceeds, it maintains its state in a SimulationStatus object. This object contains information about the rocket’s current position, orientation, velocity, simulation state, and the simulation’s event queue. It also contains a reference to a copy of the rocket design and its configuration. Any simulation listener method (see below) may modify the state of the rocket by changing the properties of the SimulationStatus object.

You can obtain current information regarding the state of the simulation by calling get*() methods, and change it with set*() methods. For instance, the rocket’s current position can be obtained by calling getRocketPosition() and changed by calling setRocketPosition(Coordinate position). All of the get*() and set*() methods can be found in core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java. Note that while some information can be obtained in this way, it is not as complete as that found in FlightData and FlightDataBranch objects to be described below.

Simulation Data

All of the data for a simulation is stored in an object of type FlightData This in turn consists of some summary data for the entire simulation, plus the data collected during the execution of the simulation.

The simulation of each stage of the rocket creates a new branch of the simulation. The data for the branch is stored in a FlightDataBranch The main content of a FlightDataBranch is a number of objects of type FlightDataType which are in turn List<Double> objects with one list for each simulation variable and one element in the list for each time step. Each simulation must have at least one FlightDataBranch.

To obtain a FlightDataType, for example the motor mass, from a FlightDataBranch called flightDataBranch, we call flightDataBranch.get(FlightDataType.TYPE_MOTOR_MASS). The standard FlightDataType lists are all created in core/src/main/java/info/openrocket/core/simulation/FlightDataType.java.

Data from the current simulation step can be obtained with e.g. flightDataBranch.getLast(FlightDataType.TYPE_MOTOR_MASS) to obtain the current motor mass.

Flight Conditions

Current data regarding the aerodynamics of the flight itself are stored in a FlightConditions object. This includes things like the velocity, angle of attack, and roll and pitch angle and rates. It also contains a reference to the current AtmosphericConditions.

Simulation Listeners

Simulation listeners are methods that OpenRocket calls at specified points in the computation to either record information or modify the simulation state. These are divided into three interface classes, named SimulationListener, SimulationComputationListener, and SimulationEventListener.

All of these interfaces are implemented by the abstract class AbstractSimulationListener. This class provides empty methods for all of the methods defined in the three interfaces, which are overridden as needed when writing a listener. A typical listener method (which is actually in the Air-start listener), would be:

public void startSimulation(SimulationStatus status) throws SimulationException {
    status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude()));
    status.setRocketVelocity(status.getRocketOrientationQuaternion().rotate(new Coordinate(0, 0, getLaunchVelocity())));
}

This method is called when the simulation is first started. It obtains the desired launch altitude and velocity from its configuration, and inserts them into the simulation status to simulate an air-start.

The full set of listener methods, with documentation regarding when they are called, can be found in core/src/main/java/info/openrocket/core/simulation/listeners/AbstractSimulationListener.java.

The listener methods can have three return value types:

  • The startSimulation(), endSimulation(), and postStep() are void methods and do not return any value.

  • The preStep() and event-related hook methods return a boolean value indicating whether the associated action should be taken or not. A return value of true indicates that the action should be taken as it normally would be (default), false will inhibit the action.

  • The pre- and post-computation methods may return the computed value, either as an object or a double value. The pre-computation methods allow pre-empting the entire computation, while the post-computation methods allow modifying the computed values. These methods may return null or Double.NaN to use the original values, or return an overriding value.

Every listener receives a SimulationStatus (see above) object as the first argument, and may also have additional arguments.

Each listener method may also throw a SimulationException. This is considered an error during simulation (not a program bug), and an error dialog is displayed to the user with the exception message. As an example, when the RollControl example simulation extension is configured, the user is required to enter the name of a fin set. If no fin set with this name is present in the rocket being simulated, a SimulationException is thrown.

Creating a New Simulation Extension

Creating an extension for OpenRocket requires writing three classes:

  • A listener, which extends AbstractSimulationListener. This will be the bulk of your extension, and performs all the real work.

  • An extension, which extends AbstractSimulationExtension. This inserts your listener into the simulation. Your listener can (and ordinarily will) be private to your extension.

  • A provider, which extends AbstractSimulationExtensionProvider. This puts your extension into the extension menu (to be described below).

In addition, if your extension will have a configuration GUI, you will need to write:

  • A configurator, which extends AbstractSwingSimulationExtensionConfigurator<E>

You can either create and build your extension outside the source tree and make sure it is in a directory that is in your Java classpath when OpenRocket is executed (for instance, the Plugins directory described above), or you can insert it in the source tree and compile it along with OpenRocket. Since all of OpenRocket’s code is freely available, and reading the code for the existing extensions will be very helpful in writing your own, the easiest approach is to simply insert it in the source tree. If you select this option, a very logical place to put your extension is in core/src/main/java/info/openrocket/core/simulation/extension/ The extension examples provided with OpenRocket are located in a subdirectory of this named example/.

Your configurator, if any, will logically go in swing/src/main/java/info/openrocket/swing/simulation/extension/ Configurators for the example extensions are located in a subdirectory of this named example/.

Extension Example

To make things concrete, we’ll create a simple example extension to air-start a rocket from a hard-coded altitude. Later, we’ll add a configurator to the extension so we can set the launch altitude through a GUI at runtime. This is a simplified version of the AirStart extension included in the extension example directory in the OpenRocket source code tree (that extension also sets a start velocity).

 1 package info.openrocket.core.simulation.extension;
 2
 3 import info.openrocket.core.simulation.SimulationConditions;
 4 import info.openrocket.core.simulation.SimulationStatus;
 5 import info.openrocket.core.simulation.exception.SimulationException;
 6 import info.openrocket.core.simulation.extension.AbstractSimulationExtension;
 7 import info.openrocket.core.simulation.listeners.AbstractSimulationListener;
 8 import info.openrocket.core.util.Coordinate;
 9
10 /**
11  * Simulation extension that launches a rocket from a specific altitude.
12  */
13 public class AirStartExample extends AbstractSimulationExtension {
14
15     @Override
16     public boolean isMonteCarloSafe() {
17         return true;
18     }
19
20     @Override
21     public void initialize(SimulationConditions conditions) throws SimulationException {
22         conditions.getSimulationListenerList().add(new AirStartListener());
23     }
24
25     @Override
26     public String getName() {
27         return "Air-Start Example";
28     }
29
30     @Override
31     public String getDescription() {
32         return "Simple extension example for air-start";
33     }
34
35     private class AirStartListener extends AbstractSimulationListener {
36
37         @Override
38         public void startSimulation(SimulationStatus status) throws SimulationException {
39             status.setRocketPosition(new Coordinate(0, 0, 1000.0));
40         }
41     }
42 }

There are several important features in this example:

  • The isMonteCarloSafe() method explicitly declares that the extension can run as part of a Monte Carlo analysis.

  • The initialize() method, which adds the listener to the simulation. This is the only method that is actually required to be defined in your extension, though any real extension (including this example) will almost certainly have more.

  • The getName() method, which provides the extension’s name. A default getName() is provided by AbstractSimulationExtension, which simply uses the classname (so for this example, getName() would have returned "AirStartExample" if this method hadn’t overridden it).

  • The getDescription() method, which provides a brief description of the purpose of the extension. This is the method that provides the text for the Info button dialog shown in the first section of this page.

  • The listener itself, which provides a single startSimulation() method. When the simulation starts executing, this listener is called, and the rocket is set to an altitude of 1000 meters.

This will create the extension when it’s compiled, but it won’t put it in the simulation extension menu. To be able to actually use it, we need a provider, like this:

1 import info.openrocket.core.plugin.Plugin;
2 import info.openrocket.core.simulation.extension.AbstractSimulationExtensionProvider;
3
4 @Plugin
5 public class AirStartExampleProvider extends AbstractSimulationExtensionProvider {
6     public AirStartExampleProvider() {
7         super(AirStartExample.class, "Launch conditions", "Air-start example");
8     }
9 }

This class adds your extension to the extension menu with the super call in line 7. The first parameter ("Launch Conditions") is the first menu level, while the second ("Air-start example") is the actual menu entry. These strings can be anything you want; using a first level entry that didn’t previously exist will add it to the first level menu.

Try it! Putting the extension in a file named core/src/main/java/info/openrocket/core/simulation/extension/AirStartExample.java and the provider in core/src/main/java/info/openrocket/core/simulation/extension/AirStartExampleProvider.java, and compiling and running OpenRocket, will give you a new entry in the extensions menu; adding it to the simulation will cause your simulation to start at an altitude of 1000 meters.

Monte Carlo Compatibility

A Monte Carlo analysis runs the nominal trajectory followed by many dispersed trajectories, potentially in parallel. Simulation extensions are included in these trajectories; they are not silently ignored. However, an extension is considered incompatible by default. If an attached extension is incompatible, OpenRocket refuses to start the analysis and identifies the extension that must be disabled.

Compatibility is declared by the extension author, not by the user. Override isMonteCarloSafe() to return true only when every invocation confines its mutations to the copied simulation and is safe to execute repeatedly and concurrently. A compatible extension must not write files, print output, access the network, display user-interface elements, or otherwise produce external side effects. It must also avoid shared mutable state between trajectories. Extensions that execute arbitrary scripts or Java code should remain incompatible.

The AirStartExample above is compatible because it changes only the state of the independently copied simulation. Its explicit override allows it to run in a Monte Carlo analysis. There is intentionally no user preference that can override an extension author’s declaration.

Adding a Configurator

To be able to configure the extension at runtime, we need to write a configurator and provide it with a way to communicate with the extension. First, we’ll modify the extension as follows:

 1 package info.openrocket.core.simulation.extension;
 2
 3 import info.openrocket.core.simulation.SimulationConditions;
 4 import info.openrocket.core.simulation.SimulationStatus;
 5 import info.openrocket.core.simulation.exception.SimulationException;
 6 import info.openrocket.core.simulation.extension.AbstractSimulationExtension;
 7 import info.openrocket.core.simulation.listeners.AbstractSimulationListener;
 8 import info.openrocket.core.util.Coordinate;
 9
10 /**
11  * Simulation extension that launches a rocket from a specific altitude.
12  */
13 public class AirStartExample extends AbstractSimulationExtension {
14
15     @Override
16     public boolean isMonteCarloSafe() {
17         return true;
18     }
19
20     @Override
21     public void initialize(SimulationConditions conditions) throws SimulationException {
22         conditions.getSimulationListenerList().add(new AirStartListener());
23     }
24
25     @Override
26     public String getName() {
27         return "Air-Start Example";
28     }
29
30     @Override
31     public String getDescription() {
32         return "Simple extension example for air-start";
33     }
34
35    public double getLaunchAltitude() {
36        return config.getDouble("launchAltitude", 1000.0);
37    }
38
39    public void setLaunchAltitude(double launchAltitude) {
40        config.put("launchAltitude", launchAltitude);
41        fireChangeEvent();
42    }
43
44     private class AirStartListener extends AbstractSimulationListener {
45
46         @Override
47         public void startSimulation(SimulationStatus status) throws SimulationException {
48             status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude()));
49         }
50     }
51 }

This adds two methods to the extension (getLaunchAltitude() and setLaunchAltitude()), and calls getLaunchAltitude() from within the listener to obtain the configured launch altitude. config is a Config object, provided by AbstractSimulationExtension (so it isn’t necessary to call a constructor yourself). core/src/main/java/info/openrocket/core/util/Config.java includes methods to interact with a configurator, allowing the extension to obtain double, string, and other configuration values.

In this case, we’ll only be defining a single configuration field in our configurator, "launchAltitude".

The getLaunchAltitude() method obtains the air-start altitude for the simulation from the configuration, and sets a default air-start altitude of 1000 meters. Our startSimulation() method has been modified to make use of this to obtain the user’s requested air-start altitude from the configurator, in place of the original hard-coded value.

The setLaunchAltitude() method places a new launch altitude in the configuration. While our extension doesn’t make use of this method directly, it will be necessary for our configurator. The call to fireChangeEvent() in this method assures that the changes we make to the air-start altitude are propagated throughout the simulation.

The configurator itself looks like this:

 1package info.openrocket.swing.simulation.extension;
 2
 3import javax.swing.JComponent;
 4import javax.swing.JLabel;
 5import javax.swing.JPanel;
 6import javax.swing.JSpinner;
 7
 8import info.openrocket.core.document.Simulation;
 9import info.openrocket.core.simulation.extension.AirStartExample;
10import info.openrocket.swing.gui.SpinnerEditor;
11import info.openrocket.swing.gui.adaptors.DoubleModel;
12import info.openrocket.swing.gui.components.BasicSlider;
13import info.openrocket.swing.gui.components.UnitSelector;
14import info.openrocket.core.plugin.Plugin;
15import info.openrocket.swing.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
16import info.openrocket.core.unit.UnitGroup;
17
18@Plugin
19public class AirStartExampleConfigurator extends AbstractSwingSimulationExtensionConfigurator<AirStartExample> {
20
21    public AirStartExampleConfigurator() {
22        super(AirStartExample.class);
23    }
24
25    @Override
26    protected JComponent getConfigurationComponent(AirStartExample extension, Simulation simulation, JPanel panel) {
27        panel.add(new JLabel("Launch altitude:"));
28
29        DoubleModel m = new DoubleModel(extension, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
30
31        JSpinner spin = new JSpinner(m.getSpinnerModel());
32        spin.setEditor(new SpinnerEditor(spin));
33        panel.add(spin, "w 65lp!");
34
35        UnitSelector unit = new UnitSelector(m);
36        panel.add(unit, "w 25");
37
38        BasicSlider slider = new BasicSlider(m.getSliderModel(0, 5000));
39        panel.add(slider, "w 75lp, wrap");
40
41        return panel;
42    }
43}

After some boilerplate, this class creates a new DoubleModel to manage the air-start altitude (line 29). The most important things to notice about the DoubleModel constructor are the parameters "LaunchAltitude" and UnitGroup.UNITS_DISTANCE.

  • "LaunchAltitude" is used by the system to synthesize calls to the getLaunchAltitude() and setLaunchAltitude() methods defined in AirStartExample above. The name of the DoubleModel, "LaunchAltitude", MUST match the names of the corresponding set and get methods exactly. If they don’t, there will be an exception at runtime when the user attempts to change the value.

  • UnitGroup.UNITS_DISTANCE specifies the unit group to be used by this DoubleModel. OpenRocket uses SI (MKS) units internally, but allows users to select the units they wish to use for their interface. Specifying a UnitGroup provides the conversions and unit displays for the interface. The available UnitGroup options are defined in core/src/main/java/info/openrocket/core/unit/UnitGroup.java

The remaining code in this method creates a JSpinner, a UnitSelector, and a BasicSlider all referring to this DoubleModel. When the resulting configurator is displayed, it looks like this:

Example configurator

Example configurator.

The surrounding Dialog window and the Close button are provided by the system.


Adding an Extension to a Simulation

An extension, whether it’s one of the included examples, a third party extension, or an extension you’ve written, is added to a simulation through a menu in the “Simulation Options” tab.

  1. Open a .ork file and go to the Flight Simulations tab.

  2. Click the Edit simulation button to open the Edit simulation dialog.

  3. Go to the Simulation options tab.

  4. Click the Add extension button.

This will open a menu similar to the one in the following screenshot:

Extension menu

Extension menu.

Clicking on the name of an extension will add it to the simulation; if it has a configuration dialog the dialog will be opened:

Air-start configuration dialog

Air-start configuration dialog.

In the case of the air-start extension included with OpenRocket, the configuration dialog allows you to set the altitude and velocity at which your simulation will begin. After you close the configuration dialog (if any), a new panel will be added to the Simulation options pane, showing the new extension with buttons to reconfigure it, obtain information about it, or remove it from the simulation:

Air-start extension pane

Air-start extension pane.