diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index be5724c5d..ad0d9e245 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -377,11 +377,9 @@ simedtdlg.lbl.Timestep = Time step:
simedtdlg.lbl.ttip.Timestep1 = The time between simulation steps.
A smaller time step results in a more accurate but slower simulation.
simedtdlg.lbl.ttip.Timestep2 = The 4th order simulation method is quite accurate with a time step of
simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
-simedtdlg.border.Simlist = Simulator listeners
-simedtdlg.txt.longA1 = Simulation listeners is an advanced feature that allows user-written code to listen to and interact with the simulation.
-simedtdlg.txt.longA2 = For details on writing simulation listeners, see the OpenRocket technical documentation.
-simedtdlg.lbl.Curlist = Current listeners:
-simedtdlg.lbl.Addsimlist = Add simulation listener
+simedtdlg.border.SimExt = Simulation extensions
+simedtdlg.SimExt.desc = Simulation extensions enable advanced features and custom functionality during flight simulations. You can for example do hardware-in-the-loop testing with them.
+simedtdlg.SimExt.noExtensions = No simulation extensions defined
simedtdlg.lbl.Noflightdata = No flight data available.
simedtdlg.lbl.runsimfirst = Please run the simulation first.
simedtdlg.chart.Simflight = Simulated flight
@@ -394,6 +392,12 @@ simedtdlg.IntensityDesc.High = High
simedtdlg.IntensityDesc.Veryhigh = Very high
simedtdlg.IntensityDesc.Extreme = Extreme
+SimulationExtension.airstart.name = Air-start ({alt})
+SimulationExtension.javacode.name = Java code
+SimulationExtension.javacode.name.none = none
+SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
+SimulationExtension.javacode.className = Fully-qualified Java class name:
+
SimulationEditDialog.btn.plot = Plot
SimulationEditDialog.btn.export = Export
SimulationEditDialog.btn.edit = Edit
diff --git a/core/resources/pix/icons/configure.png b/core/resources/pix/icons/configure.png
new file mode 100644
index 000000000..a4a3834ab
Binary files /dev/null and b/core/resources/pix/icons/configure.png differ
diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java
index c479005b9..aec3d0333 100644
--- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java
+++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java
@@ -23,7 +23,7 @@ import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
-import net.sf.openrocket.simulation.listeners.SimulationListener;
+import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@@ -146,17 +146,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
// simulation listeners
for (Simulation sim : simulations) {
- for (String className : sim.getSimulationListeners()) {
- SimulationListener l = null;
- try {
- Class> c = Class.forName(className);
- l = (SimulationListener) c.newInstance();
-
- Collections.addAll(allTypes, l.getFlightDataTypes());
- //System.out.println("This document has expression datatype from "+l.getName());
- } catch (Exception e) {
- log.error("Could not instantiate listener: " + className);
- }
+ for (SimulationExtension c : sim.getSimulationExtensions()) {
+ allTypes.addAll(c.getFlightDataTypes());
}
}
diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java
index 594734e2b..0b672032e 100644
--- a/core/src/net/sf/openrocket/document/Simulation.java
+++ b/core/src/net/sf/openrocket/document/Simulation.java
@@ -21,7 +21,7 @@ import net.sf.openrocket.simulation.SimulationEngine;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.SimulationStepper;
import net.sf.openrocket.simulation.exception.SimulationException;
-import net.sf.openrocket.simulation.exception.SimulationListenerException;
+import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@@ -76,7 +76,8 @@ public class Simulation implements ChangeSource, Cloneable {
// TODO: HIGH: Change to use actual conditions class??
private SimulationOptions options;
- private ArrayList simulationListeners = new ArrayList();
+ private ArrayList simulationExtensions = new ArrayList();
+
private final Class extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
private Class extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
@@ -116,7 +117,7 @@ public class Simulation implements ChangeSource, Cloneable {
public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
- List listeners, FlightData data) {
+ List extensions, FlightData data) {
if (rocket == null)
throw new IllegalArgumentException("rocket cannot be null");
@@ -142,8 +143,8 @@ public class Simulation implements ChangeSource, Cloneable {
this.options = options;
options.addChangeListener(new ConditionListener());
- if (listeners != null) {
- this.simulationListeners.addAll(listeners);
+ if (extensions != null) {
+ this.simulationExtensions.addAll(extensions);
}
@@ -196,14 +197,14 @@ public class Simulation implements ChangeSource, Cloneable {
/**
- * Get the list of simulation listeners. The returned list is the one used by
+ * Get the list of simulation extensions. The returned list is the one used by
* this object; changes to it will reflect changes in the simulation.
*
- * @return the actual list of simulation listeners.
+ * @return the actual list of simulation extensions.
*/
- public List getSimulationListeners() {
+ public List getSimulationExtensions() {
mutex.verify();
- return simulationListeners;
+ return simulationExtensions;
}
@@ -293,16 +294,8 @@ public class Simulation implements ChangeSource, Cloneable {
simulationConditions.getSimulationListenerList().add(l);
}
- for (String className : simulationListeners) {
- SimulationListener l = null;
- try {
- Class> c = Class.forName(className);
- l = (SimulationListener) c.newInstance();
- } catch (Exception e) {
- throw new SimulationListenerException("Could not instantiate listener of " +
- "class: " + className, e);
- }
- simulationConditions.getSimulationListenerList().add(l);
+ for (SimulationExtension extension : simulationExtensions) {
+ extension.initialize(simulationConditions);
}
long t1, t2;
@@ -410,7 +403,9 @@ public class Simulation implements ChangeSource, Cloneable {
copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED;
copy.options = this.options.clone();
- copy.simulationListeners = this.simulationListeners.clone();
+ for (SimulationExtension c : this.simulationExtensions) {
+ copy.simulationExtensions.add(c.clone());
+ }
copy.listeners = new ArrayList();
copy.simulatedConditions = null;
copy.simulatedConfiguration = null;
@@ -442,7 +437,9 @@ public class Simulation implements ChangeSource, Cloneable {
copy.name = this.name;
copy.options.copyFrom(this.options);
copy.simulatedConfiguration = this.simulatedConfiguration;
- copy.simulationListeners = this.simulationListeners.clone();
+ for (SimulationExtension c : this.simulationExtensions) {
+ copy.simulationExtensions.add(c.clone());
+ }
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
index a3f085331..108c634fa 100644
--- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
+++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
@@ -30,8 +30,10 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.BuildProperties;
+import net.sf.openrocket.util.Config;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.TextUtil;
@@ -455,9 +457,18 @@ public class OpenRocketSaver extends RocketSaver {
indent--;
writeln("");
-
- for (String s : simulation.getSimulationListeners()) {
- writeElement("listener", TextUtil.escapeXML(s));
+ for (SimulationExtension extension : simulation.getSimulationExtensions()) {
+ Config config = extension.getConfig();
+ writeln("");
+ indent++;
+ if (config != null) {
+ for (String key : config.keySet()) {
+ Object value = config.get(key, null);
+ writeEntry(key, value);
+ }
+ }
+ indent--;
+ writeln("");
}
// Write basic simulation data
@@ -512,6 +523,39 @@ public class OpenRocketSaver extends RocketSaver {
}
+ private void writeEntry(String key, Object value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ String keyAttr;
+
+ if (key != null) {
+ keyAttr = "key=\"" + key + "\" ";
+ } else {
+ keyAttr = "";
+ }
+
+ if (value instanceof Boolean) {
+ writeln("" + value + "");
+ } else if (value instanceof Number) {
+ writeln("" + value + "");
+ } else if (value instanceof String) {
+ writeln("" + value + "");
+ } else if (value instanceof List) {
+ List> list = (List>) value;
+ writeln("");
+ indent++;
+ for (Object o : list) {
+ writeEntry(null, o);
+ }
+ indent--;
+ writeln("");
+ } else {
+ // Unknown type
+ log.error("Unknown configuration value type " + value.getClass() + " value=" + value);
+ }
+ }
+
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
throws IOException {
double previousTime = -100000;
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java
new file mode 100644
index 000000000..3ecc6cc35
--- /dev/null
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java
@@ -0,0 +1,104 @@
+package net.sf.openrocket.file.openrocket.importt;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.file.simplesax.AbstractElementHandler;
+import net.sf.openrocket.file.simplesax.ElementHandler;
+import net.sf.openrocket.file.simplesax.PlainTextHandler;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.Config;
+
+import org.xml.sax.SAXException;
+
+public class ConfigHandler extends AbstractElementHandler {
+
+ private ConfigHandler listHandler;
+ private Config config = new Config();
+ private List