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.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.export = Export
|
||||
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.ThrustCurveMotorSetDatabase;
|
||||
import net.sf.openrocket.l10n.ClassBasedTranslator;
|
||||
import net.sf.openrocket.l10n.DebugTranslator;
|
||||
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
|
||||
@ -33,6 +34,10 @@ public final class Application {
|
||||
}
|
||||
|
||||
private static Translator getBaseTranslator() {
|
||||
if (injector == null) {
|
||||
// Occurs in some unit tests
|
||||
return new DebugTranslator(null);
|
||||
}
|
||||
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