Initial implementation of scripting extension
This commit is contained in:
parent
78cdd77d78
commit
a2c0af3b7f
@ -405,6 +405,10 @@ SimulationExtension.javacode.name.none = none
|
|||||||
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
|
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
|
||||||
SimulationExtension.javacode.className = Fully-qualified Java class name:
|
SimulationExtension.javacode.className = Fully-qualified Java class name:
|
||||||
|
|
||||||
|
SimulationExtension.scripting.name = {language} script
|
||||||
|
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
|
||||||
|
SimulationExtension.scripting.script.label = JavaScript code:
|
||||||
|
|
||||||
SimulationEditDialog.btn.plot = Plot
|
SimulationEditDialog.btn.plot = Plot
|
||||||
SimulationEditDialog.btn.export = Export
|
SimulationEditDialog.btn.export = Export
|
||||||
SimulationEditDialog.btn.edit = Edit
|
SimulationEditDialog.btn.edit = Edit
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.sf.openrocket.simulation.extension.impl;
|
||||||
|
|
||||||
|
import javax.script.Invocable;
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
import net.sf.openrocket.l10n.L10N;
|
||||||
|
import net.sf.openrocket.simulation.SimulationConditions;
|
||||||
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
|
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
|
||||||
|
import net.sf.openrocket.simulation.listeners.SimulationListener;
|
||||||
|
|
||||||
|
public class ScriptingExtension extends AbstractSimulationExtension {
|
||||||
|
|
||||||
|
private static final String JS = "JavaScript";
|
||||||
|
|
||||||
|
public ScriptingExtension() {
|
||||||
|
setLanguage(JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
String name = trans.get("SimulationExtension.scripting.name");
|
||||||
|
name = L10N.replace(name, "{language}", getLanguage());
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return trans.get("SimulationExtension.scripting.desc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(SimulationConditions conditions) throws SimulationException {
|
||||||
|
conditions.getSimulationListenerList().add(getListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getScript() {
|
||||||
|
return config.getString("script", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScript(String script) {
|
||||||
|
config.put("script", script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLanguage() {
|
||||||
|
// TODO: Support other languages
|
||||||
|
return JS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLanguage(String language) {
|
||||||
|
config.put("language", language);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SimulationListener getListener() throws SimulationException {
|
||||||
|
ScriptEngineManager manager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = manager.getEngineByName("JavaScript");
|
||||||
|
|
||||||
|
try {
|
||||||
|
engine.eval(getScript());
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
throw new SimulationException("Invalid script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check for implementation first
|
||||||
|
return new ScriptingSimulationListener((Invocable) engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.sf.openrocket.simulation.extension.impl;
|
||||||
|
|
||||||
|
import net.sf.openrocket.plugin.Plugin;
|
||||||
|
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
|
||||||
|
|
||||||
|
@Plugin
|
||||||
|
public class ScriptingProvider extends AbstractSimulationExtensionProvider {
|
||||||
|
|
||||||
|
public ScriptingProvider() {
|
||||||
|
super(ScriptingExtension.class, "User code", "Scripts");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,217 @@
|
|||||||
|
package net.sf.openrocket.simulation.extension.impl;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.script.Invocable;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
|
import net.sf.openrocket.motor.MotorId;
|
||||||
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
|
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||||
|
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||||
|
import net.sf.openrocket.simulation.AccelerationData;
|
||||||
|
import net.sf.openrocket.simulation.FlightEvent;
|
||||||
|
import net.sf.openrocket.simulation.MassData;
|
||||||
|
import net.sf.openrocket.simulation.SimulationStatus;
|
||||||
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
|
import net.sf.openrocket.simulation.listeners.SimulationComputationListener;
|
||||||
|
import net.sf.openrocket.simulation.listeners.SimulationEventListener;
|
||||||
|
import net.sf.openrocket.simulation.listeners.SimulationListener;
|
||||||
|
import net.sf.openrocket.util.BugException;
|
||||||
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ScriptingSimulationListener implements SimulationListener, SimulationComputationListener, SimulationEventListener, Cloneable {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(ScriptingSimulationListener.class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: This class is used instead of using the scripting interface API
|
||||||
|
* so that unimplemented script methods are not called unnecessarily.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Invocable invocable;
|
||||||
|
private Set<String> missing = new HashSet<String>();
|
||||||
|
|
||||||
|
|
||||||
|
public ScriptingSimulationListener(Invocable invocable) {
|
||||||
|
this.invocable = invocable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSystemListener() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimulationListener clone() {
|
||||||
|
try {
|
||||||
|
ScriptingSimulationListener clone = (ScriptingSimulationListener) super.clone();
|
||||||
|
clone.missing = new HashSet<String>(missing);
|
||||||
|
return clone;
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
throw new BugException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//// SimulationListener ////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startSimulation(SimulationStatus status) throws SimulationException {
|
||||||
|
invoke(null, "startSimulation", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endSimulation(SimulationStatus status, SimulationException exception) {
|
||||||
|
try {
|
||||||
|
invoke(null, "endSimulation", status, exception);
|
||||||
|
} catch (SimulationException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preStep(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(true, "preStep", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postStep(SimulationStatus status) throws SimulationException {
|
||||||
|
invoke(null, "postStep", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//// SimulationEventListener ////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
|
||||||
|
return invoke(true, "addFlightEvent", status, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
|
||||||
|
return invoke(true, "handleFlightEvent", status, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException {
|
||||||
|
return invoke(true, "motorIgnition", status, motorId, mount, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
|
||||||
|
return invoke(true, "recoveryDeviceDeployment", status, recoveryDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//// SimulationComputationListener ////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preAccelerationCalculation", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preAerodynamicCalculation", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preAtmosphericModel", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preFlightConditions", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double preGravityModel(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(Double.NaN, "preGravityModel", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preMassCalculation", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(Double.NaN, "preSimpleThrustCalculation", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate preWindModel(SimulationStatus status) throws SimulationException {
|
||||||
|
return invoke(null, "preWindModel", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException {
|
||||||
|
return invoke(null, "postAccelerationCalculation", status, acceleration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException {
|
||||||
|
return invoke(null, "postAerodynamicCalculation", status, forces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException {
|
||||||
|
return invoke(null, "postAtmosphericModel", status, atmosphericConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
|
||||||
|
return invoke(null, "postFlightConditions", status, flightConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException {
|
||||||
|
return invoke(Double.NaN, "postGravityModel", status, gravity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
|
||||||
|
return invoke(null, "postMassCalculation", status, massData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
|
||||||
|
return invoke(Double.NaN, "postSimpleThrustCalculation", status, thrust);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
|
||||||
|
return invoke(null, "postWindModel", status, wind);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T invoke(T def, String method, Object... args) throws SimulationException {
|
||||||
|
try {
|
||||||
|
if (!missing.contains(method)) {
|
||||||
|
return (T) invocable.invokeFunction(method, args);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
missing.add(method);
|
||||||
|
// fall-through
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
logger.warn("Script exception in " + method + ": " + e, e);
|
||||||
|
throw new SimulationException("Script failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,6 +4,7 @@ import net.sf.openrocket.database.ComponentPresetDao;
|
|||||||
import net.sf.openrocket.database.motor.MotorDatabase;
|
import net.sf.openrocket.database.motor.MotorDatabase;
|
||||||
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
|
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
|
||||||
import net.sf.openrocket.l10n.ClassBasedTranslator;
|
import net.sf.openrocket.l10n.ClassBasedTranslator;
|
||||||
|
import net.sf.openrocket.l10n.DebugTranslator;
|
||||||
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
|
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
|
||||||
import net.sf.openrocket.l10n.Translator;
|
import net.sf.openrocket.l10n.Translator;
|
||||||
|
|
||||||
@ -33,6 +34,10 @@ public final class Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Translator getBaseTranslator() {
|
private static Translator getBaseTranslator() {
|
||||||
|
if (injector == null) {
|
||||||
|
// Occurs in some unit tests
|
||||||
|
return new DebugTranslator(null);
|
||||||
|
}
|
||||||
return injector.getInstance(Translator.class);
|
return injector.getInstance(Translator.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package net.sf.openrocket.simulation.extension.impl;
|
||||||
|
|
||||||
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.FocusListener;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
|
||||||
|
import net.sf.openrocket.document.Simulation;
|
||||||
|
import net.sf.openrocket.gui.components.StyledLabel;
|
||||||
|
import net.sf.openrocket.gui.components.StyledLabel.Style;
|
||||||
|
import net.sf.openrocket.gui.util.GUIUtil;
|
||||||
|
import net.sf.openrocket.plugin.Plugin;
|
||||||
|
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
|
||||||
|
|
||||||
|
@Plugin
|
||||||
|
public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfigurator<ScriptingExtension> {
|
||||||
|
|
||||||
|
protected ScriptingConfigurator() {
|
||||||
|
super(ScriptingExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JComponent getConfigurationComponent(final ScriptingExtension extension, Simulation simulation, JPanel panel) {
|
||||||
|
|
||||||
|
panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.script.label"), Style.BOLD), "wrap");
|
||||||
|
|
||||||
|
final JTextArea text = new JTextArea(extension.getScript(), 20, 60);
|
||||||
|
text.setLineWrap(true);
|
||||||
|
text.setWrapStyleWord(true);
|
||||||
|
text.setEditable(true);
|
||||||
|
GUIUtil.setTabToFocusing(text);
|
||||||
|
text.addFocusListener(new FocusListener() {
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent event) {
|
||||||
|
String str = text.getText();
|
||||||
|
if (!extension.getScript().equals(str)) {
|
||||||
|
extension.setScript(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.add(new JScrollPane(text), "grow");
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user