Merge remote-tracking branch 'origin/unstable' into rework-766

This commit is contained in:
JoePfeiffer 2022-12-14 13:45:03 -07:00
commit c3a2552ee3
25 changed files with 790 additions and 399 deletions

View File

@ -1,4 +1,4 @@
package net.sf.openrocket.simulation.extension.impl;
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions;
@ -28,6 +28,11 @@ public class AirStart extends AbstractSimulationExtension {
name = L10N.replace(name, "{vel}", UnitGroup.UNITS_VELOCITY.toStringUnit(getLaunchVelocity()));
return name;
}
@Override
public String getDescription() {
return "Start simulation with a configurable altitude and velocity";
}
public double getLaunchAltitude() {
return config.getDouble("launchAltitude", 0.0);

View File

@ -1,4 +1,4 @@
package net.sf.openrocket.simulation.extension.impl;
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;

View File

@ -1,4 +1,4 @@
package net.sf.openrocket.simulation.listeners.example;
package net.sf.openrocket.simulation.extension.example;
import java.io.File;
import java.io.FileNotFoundException;
@ -9,12 +9,14 @@ import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
public class CSVSaveListener extends AbstractSimulationListener {
public class CSVSave extends AbstractSimulationExtension {
private static enum Types {
TIME {
@ -218,78 +220,89 @@ public class CSVSaveListener extends AbstractSimulationListener {
private File file;
private PrintStream output = null;
@Override
public String getDescription() {
return "dump a CSV file with a predetermined set of flight variables to a CSV file while running";
}
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new CSVSaveListener());
}
private class CSVSaveListener extends AbstractSimulationListener {
if (event.getType() == FlightEvent.Type.LAUNCH) {
int n = 1;
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
if (output != null) {
System.err.println("WARNING: Ending simulation logging to CSV file " +
"(SIMULATION_END not encountered).");
if (event.getType() == FlightEvent.Type.LAUNCH) {
int n = 1;
if (output != null) {
System.err.println("WARNING: Ending simulation logging to CSV file " +
"(SIMULATION_END not encountered).");
output.close();
output = null;
}
do {
file = new File(String.format(FILENAME_FORMAT, n));
n++;
} while (file.exists());
System.err.println("Opening file " + file + " for CSV output.");
try {
output = new PrintStream(file);
} catch (FileNotFoundException e) {
System.err.println("ERROR OPENING FILE: " + e);
}
final Types[] types = Types.values();
StringBuilder s = new StringBuilder("# " + types[0].toString());
for (int i = 1; i < types.length; i++) {
s.append("," + types[i].toString());
}
output.println(s);
} else if (event.getType() == FlightEvent.Type.SIMULATION_END && output != null) {
System.err.println("Ending simulation logging to CSV file: " + file);
output.close();
output = null;
} else if (event.getType() != FlightEvent.Type.ALTITUDE) {
if (output != null) {
output.println("# Event " + event);
} else {
System.err.println("WARNING: Event " + event + " encountered without open file");
}
}
do {
file = new File(String.format(FILENAME_FORMAT, n));
n++;
} while (file.exists());
System.err.println("Opening file " + file + " for CSV output.");
try {
output = new PrintStream(file);
} catch (FileNotFoundException e) {
System.err.println("ERROR OPENING FILE: " + e);
}
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
final Types[] types = Types.values();
StringBuilder s = new StringBuilder("# " + types[0].toString());
for (int i = 1; i < types.length; i++) {
s.append("," + types[i].toString());
}
output.println(s);
} else if (event.getType() == FlightEvent.Type.SIMULATION_END && output != null) {
System.err.println("Ending simulation logging to CSV file: " + file);
output.close();
output = null;
} else if (event.getType() != FlightEvent.Type.ALTITUDE) {
StringBuilder s;
if (output != null) {
output.println("# Event " + event);
s = new StringBuilder("" + types[0].getValue(status));
for (int i = 1; i < types.length; i++) {
s.append("," + types[i].getValue(status));
}
output.println(s);
} else {
System.err.println("WARNING: Event " + event + " encountered without open file");
System.err.println("WARNING: stepTaken called with no open file " +
"(t=" + status.getSimulationTime() + ")");
}
}
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
final Types[] types = Types.values();
StringBuilder s;
if (output != null) {
s = new StringBuilder("" + types[0].getValue(status));
for (int i = 1; i < types.length; i++) {
s.append("," + types[i].getValue(status));
}
output.println(s);
} else {
System.err.println("WARNING: stepTaken called with no open file " +
"(t=" + status.getSimulationTime() + ")");
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class CSVSaveProvider extends AbstractSimulationExtensionProvider {
public CSVSaveProvider() {
super(CSVSave.class, "Reports", "CSV Save");
}
}

View File

@ -0,0 +1,147 @@
package net.sf.openrocket.simulation.extension.example;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
public class DampingMoment extends AbstractSimulationExtension {
private static final Logger log = LoggerFactory.getLogger(DampingMoment.class);
// Save it as a FlightDataType
private static final FlightDataType cdm = FlightDataType.getType("Damping moment coefficient", "Cdm", UnitGroup.UNITS_COEFFICIENT);
private static final ArrayList<FlightDataType> types = new ArrayList<FlightDataType>();
DampingMoment() {
types.add(cdm);
}
@Override
public List<FlightDataType> getFlightDataTypes() {
return types;
}
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
log.debug("initializing...");
conditions.getSimulationListenerList().add(new DampingMomentListener());
}
@Override
public String getName() {
return "Damping Moment Coeficient(Cdm)";
}
@Override
public String getDescription() {
return "Calculate damping moment coefficient after every simulation step and publish to FlightData as Cdm";
}
private class DampingMomentListener extends AbstractSimulationListener {
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
//status.getFlightData().setValue(cdm, aerodynamicPart + propulsivePart);
status.getFlightData().setValue(cdm, calculate(status, flightConditions));
return flightConditions;
}
private double calculate(SimulationStatus status, FlightConditions flightConditions) {
// Work out the propulsive/jet damping part of the moment.
// dm/dt = (thrust - ma)/v
FlightDataBranch data = status.getFlightData();
List<Double> mpAll = data.get(FlightDataType.TYPE_MOTOR_MASS);
List<Double> time = data.get(FlightDataType.TYPE_TIME);
if (mpAll == null || time == null) {
return Double.NaN;
}
int len = mpAll.size();
// This isn't as accurate as I would like
double mdot = Double.NaN;
if (len > 2) {
// Using polynomial interpolator for derivative. Doesn't help much
//double[] x = { time.get(len-5), time.get(len-4), time.get(len-3), time.get(len-2), time.get(len-1) };
//double[] y = { mpAll.get(len-5), mpAll.get(len-4), mpAll.get(len-3), mpAll.get(len-2), mpAll.get(len-1) };
//PolyInterpolator interp = new PolyInterpolator(x);
//double[] coeff = interp.interpolator(y);
//double dt = .01;
//mdot = (interp.eval(x[4], coeff) - interp.eval(x[4]-dt, coeff))/dt;
mdot = (mpAll.get(len - 1) - mpAll.get(len - 2)) / (time.get(len - 1) - time.get(len - 2));
}
double cg = data.getLast(FlightDataType.TYPE_CG_LOCATION);
// find the maximum distance from nose to nozzle.
double nozzleDistance = 0;
FlightConfiguration config = status.getConfiguration();
for (MotorConfiguration inst : config.getActiveMotors()) {
double x_position= inst.getX();
double x = x_position + inst.getMotor().getLaunchCGx();
if (x > nozzleDistance) {
nozzleDistance = x;
}
}
// now can get the propulsive part
double propulsivePart = mdot * Math.pow(nozzleDistance - cg, 2);
// Work out the aerodynamic part of the moment.
double aerodynamicPart = 0;
AerodynamicCalculator aerocalc = status.getSimulationConditions().getAerodynamicCalculator();
// Must go through each component ...
Map<RocketComponent, AerodynamicForces> forces = aerocalc.getForceAnalysis(status.getConfiguration(), flightConditions, status.getWarnings());
for (Map.Entry<RocketComponent, AerodynamicForces> entry : forces.entrySet()) {
RocketComponent comp = entry.getKey();
if (!comp.isAerodynamic())
continue;
//System.out.println(comp.toString());
double CNa = entry.getValue().getCNa(); //?
double Cp = entry.getValue().getCP().length();
double z = comp.getAxialOffset();
aerodynamicPart += CNa * Math.pow(z - Cp, 2);
}
double v = flightConditions.getVelocity();
double rho = flightConditions.getAtmosphericConditions().getDensity();
double ar = flightConditions.getRefArea();
aerodynamicPart = aerodynamicPart * .5 * rho * v * ar;
return aerodynamicPart + propulsivePart;
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class DampingMomentProvider extends AbstractSimulationExtensionProvider {
public DampingMomentProvider() {
super(DampingMoment.class, "Post-step flight conditions", "Damping Moment Coefficient (Cdm)");
}
}

View File

@ -0,0 +1,53 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
public class PrintSimulation extends AbstractSimulationExtension {
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new PrintSimulationListener());
}
@Override
public String getName() {
return "Print Simulation Values";
}
@Override
public String getDescription() {
return "Print summary of simulation progress to standard output after every step";
}
private class PrintSimulationListener extends AbstractSimulationListener {
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
System.out.println("*** handleEvent *** " + event.toString() +
" position=" + status.getRocketPosition() + " velocity=" + status.getRocketVelocity());
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
FlightDataBranch data = status.getFlightData();
System.out.printf("*** stepTaken *** time=%.3f position=" + status.getRocketPosition() +
" velocity=" + status.getRocketVelocity() + "=%.3f\n", status.getSimulationTime(), status.getRocketVelocity().length());
System.out.printf(" thrust=%.3fN drag==%.3fN mass=%.3fkg " +
"accZ=%.3fm/s2 acc=%.3fm/s2\n",
data.getLast(FlightDataType.TYPE_THRUST_FORCE),
data.getLast(FlightDataType.TYPE_DRAG_FORCE),
data.getLast(FlightDataType.TYPE_MASS),
data.getLast(FlightDataType.TYPE_ACCELERATION_Z),
data.getLast(FlightDataType.TYPE_ACCELERATION_TOTAL));
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class PrintSimulationProvider extends AbstractSimulationExtensionProvider {
public PrintSimulationProvider() {
super(PrintSimulation.class, "Reports", "Print Simulation");
}
}

View File

@ -0,0 +1,206 @@
package net.sf.openrocket.simulation.extension.example;
import java.util.ArrayList;
import java.util.List;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.MathUtil;
/**
* An example listener that applies a PI-controller to adjust the cant of fins
* to control the rocket's roll rate
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*
* One note: making aerodynamic changes during the simulation results in the simulation
* running *extremely* slowly
*/
public class RollControl extends AbstractSimulationExtension {
// save fin cant angle as a FlightDataType
private static final ArrayList<FlightDataType> types = new ArrayList<FlightDataType>();
private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", "\u03B1fc", UnitGroup.UNITS_ANGLE);
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new RollControlListener());
}
@Override
public String getName() {
return "Roll Control";
}
@Override
public String getDescription() {
return "Use a PID control to control a rocket's roll. The current cant angle of the control finset is published to flight data as \u03B1fc. "
+ "Since this extension modifies design parameters during the simulation, it causes the simulation to run <b>much</b> more slowly.";
}
@Override
public List<FlightDataType> getFlightDataTypes() {
return types;
}
RollControl() {
types.add(FIN_CANT_TYPE);
}
public String getControlFinName() {
return config.getString("controlFinName", "CONTROL");
}
public void setControlFinName(String name) {
config.put("controlFinName", name);
fireChangeEvent();
}
public double getStartTime() {
return config.getDouble("startTime", 0.5);
}
public void setStartTime(double startTime) {
config.put("startTime", startTime);
fireChangeEvent();
}
// Desired roll rate (rad/sec)
public double getSetPoint() {
return config.getDouble("setPoint", 0.0);
}
public void setSetPoint(double rollRate) {
config.put("setPoint", rollRate);
fireChangeEvent();
}
// Maximum control fin turn rate (rad/sec)
public double getFinRate() {
return config.getDouble("finRate", 10 * Math.PI/180);
}
public void setFinRate(double finRate) {
config.put("finRate", finRate);
fireChangeEvent();
}
// Maximum control fin angle (rad)
public double getMaxFinAngle() {
return config.getDouble("maxFinAngle", 15 * Math.PI / 180);
}
public void setMaxFinAngle(double maxFin) {
config.put("maxFinAngle", maxFin);
fireChangeEvent();
}
public double getKP() {
return config.getDouble("KP", 0.007);
}
public void setKP(double KP) {
config.put("KP", KP);
fireChangeEvent();
}
public double getKI() {
return config.getDouble("KI", 0.2);
}
public void setKI(double KI) {
config.put("KI", KI);
fireChangeEvent();
}
private class RollControlListener extends AbstractSimulationListener {
private FinSet finset;
private double rollRate;
private double prevTime = 0;
private double intState = 0;
private double initialFinPosition;
private double finPosition = 0;
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
// Find the fin set
finset = null;
for (RocketComponent c : status.getConfiguration().getActiveComponents()) {
if ((c instanceof FinSet) && c.getName().equals(getControlFinName())) {
finset = (FinSet) c;
break;
}
}
if (finset == null) {
throw new SimulationException("A fin set with name '" + getControlFinName() + "' was not found");
}
// remember the initial fin position so we can set it back after running the simulation
initialFinPosition = finset.getCantAngle();
}
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) {
// Store the current roll rate for later use
rollRate = flightConditions.getRollRate();
return null;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
// Activate PID controller only after a specific time
if (status.getSimulationTime() < getStartTime()) {
prevTime = status.getSimulationTime();
return;
}
// Determine time step
double deltaT = status.getSimulationTime() - prevTime;
prevTime = status.getSimulationTime();
// PID controller
double error = getSetPoint() - rollRate;
double p = getKP() * error;
intState += error * deltaT;
double i = getKI() * intState;
double value = p + i;
// Limit the fin turn rate
if (finPosition < value) {
finPosition = Math.min(finPosition + getFinRate() * deltaT, value);
} else {
finPosition = Math.max(finPosition - getFinRate() * deltaT, value);
}
// Clamp the fin angle between bounds
if (Math.abs(value) > getMaxFinAngle()) {
System.err.printf("Attempting to set angle %.1f at t=%.3f, clamping.\n",
value * 180 / Math.PI, status.getSimulationTime());
value = MathUtil.clamp(value, -getMaxFinAngle(), getMaxFinAngle());
}
// Set the control fin cant and store the data
finset.setCantAngle(finPosition);
status.getFlightData().setValue(FIN_CANT_TYPE, finPosition);
}
@Override
public void endSimulation(SimulationStatus status, SimulationException exception) {
finset.setCantAngle(initialFinPosition);
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class RollControlProvider extends AbstractSimulationExtensionProvider {
public RollControlProvider() {
super(RollControl.class, "Control Enhancements", "Roll Control");
}
}

View File

@ -0,0 +1,95 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
/**
* A simulation listener that stops the simulation after a specified number of steps or
* after a specified amount of simulation time.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class StopSimulation extends AbstractSimulationExtension {
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new StopSimulationListener());
}
@Override
public String getName() {
return "Stop Simulation";
}
@Override
public String getDescription() {
return "Stop simulafter at a configurable simulation time or step count";
}
public int getReportRate() {
return config.getInt("reportRate", 500);
}
public void setReportRate(int reportRate) {
config.put("reportRate", reportRate);
}
public int getStopStep() {
return config.getInt("stopStep", 5000);
}
public void setStopStep(int stopStep) {
config.put("stopStep", stopStep);
}
public int getStopTime() {
return config.getInt("stopTime", 10);
}
public void setStopTime(int stopTime) {
config.put("stopTime", stopTime);
}
private class StopSimulationListener extends AbstractSimulationListener {
private int step = 0;
private long startTime = -1;
private long time = -1;
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
if (event.getType() == FlightEvent.Type.LAUNCH) {
System.out.println("Simulation starting.");
time = System.nanoTime();
startTime = System.nanoTime();
}
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
step++;
if ((step % getReportRate()) == 0) {
long t = System.nanoTime();
System.out.printf("Step %4d, time=%.3f, took %d us/step (avg. %d us/step)\n",
step, status.getSimulationTime(), (t - time) / 1000 / getReportRate(), (t - startTime) / 1000 / step);
time = t;
}
if (status.getSimulationTime() >= getStopTime() || step >= getStopStep()) {
System.out.printf("Stopping simulation, step=%d time=%.3f\n", step, status.getSimulationTime());
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END,
status.getSimulationTime(), null));
}
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.example;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class StopSimulationProvider extends AbstractSimulationExtensionProvider {
public StopSimulationProvider() {
super(StopSimulation.class, "Simulation Conditions", "Stop Simulation");
}
}

View File

@ -7,7 +7,7 @@ import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvide
public class JavaCodeProvider extends AbstractSimulationExtensionProvider {
public JavaCodeProvider() {
super(JavaCode.class, "User code", "Java code");
super(JavaCode.class, "Scripts", "Java listeners");
}
}

View File

@ -7,7 +7,7 @@ import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvide
public class ScriptingProvider extends AbstractSimulationExtensionProvider {
public ScriptingProvider() {
super(ScriptingExtension.class, "User code", "Scripts");
super(ScriptingExtension.class, "Scripts", "JavaScript");
}
}

View File

@ -1,116 +0,0 @@
package net.sf.openrocket.simulation.listeners.example;
import java.util.List;
import java.util.Map;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
public class DampingMoment extends AbstractSimulationListener {
private static final FlightDataType type = FlightDataType.getType("Damping moment coefficient", "Cdm", UnitGroup.UNITS_COEFFICIENT);
// unused
//private static final FlightDataType[] typeList = { type };
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
// Save it as a flightdatatype
//status.getFlightData().setValue(type, aerodynamicPart + propulsivePart);
status.getFlightData().setValue(type, calculate(status, flightConditions));
return flightConditions;
}
private double calculate(SimulationStatus status, FlightConditions flightConditions) {
// Work out the propulsive/jet damping part of the moment.
// dm/dt = (thrust - ma)/v
FlightDataBranch data = status.getFlightData();
List<Double> mpAll = data.get(FlightDataType.TYPE_MOTOR_MASS);
List<Double> time = data.get(FlightDataType.TYPE_TIME);
if (mpAll == null || time == null) {
return Double.NaN;
}
int len = mpAll.size();
// This isn't as accurate as I would like
double mdot = Double.NaN;
if (len > 2) {
// Using polynomial interpolator for derivative. Doesn't help much
//double[] x = { time.get(len-5), time.get(len-4), time.get(len-3), time.get(len-2), time.get(len-1) };
//double[] y = { mpAll.get(len-5), mpAll.get(len-4), mpAll.get(len-3), mpAll.get(len-2), mpAll.get(len-1) };
//PolyInterpolator interp = new PolyInterpolator(x);
//double[] coeff = interp.interpolator(y);
//double dt = .01;
//mdot = (interp.eval(x[4], coeff) - interp.eval(x[4]-dt, coeff))/dt;
mdot = (mpAll.get(len - 1) - mpAll.get(len - 2)) / (time.get(len - 1) - time.get(len - 2));
}
double cg = data.getLast(FlightDataType.TYPE_CG_LOCATION);
// find the maximum distance from nose to nozzle.
double nozzleDistance = 0;
FlightConfiguration config = status.getConfiguration();
for (MotorConfiguration inst : config.getActiveMotors()) {
double x_position= inst.getX();
double x = x_position + inst.getMotor().getLaunchCGx();
if (x > nozzleDistance) {
nozzleDistance = x;
}
}
// now can get the propulsive part
double propulsivePart = mdot * Math.pow(nozzleDistance - cg, 2);
// Work out the aerodynamic part of the moment.
double aerodynamicPart = 0;
AerodynamicCalculator aerocalc = status.getSimulationConditions().getAerodynamicCalculator();
// Must go through each component ...
Map<RocketComponent, AerodynamicForces> forces = aerocalc.getForceAnalysis(status.getConfiguration(), flightConditions, status.getWarnings());
for (Map.Entry<RocketComponent, AerodynamicForces> entry : forces.entrySet()) {
RocketComponent comp = entry.getKey();
if (!comp.isAerodynamic())
continue;
//System.out.println(comp.toString());
double CNa = entry.getValue().getCNa(); //?
double Cp = entry.getValue().getCP().length();
double z = comp.getAxialOffset();
aerodynamicPart += CNa * Math.pow(z - Cp, 2);
}
double v = flightConditions.getVelocity();
double rho = flightConditions.getAtmosphericConditions().getDensity();
double ar = flightConditions.getRefArea();
aerodynamicPart = aerodynamicPart * .5 * rho * v * ar;
return aerodynamicPart + propulsivePart;
}
}

View File

@ -1,34 +0,0 @@
package net.sf.openrocket.simulation.listeners.example;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
public class PrintSimulationListener extends AbstractSimulationListener {
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
System.out.println("*** handleEvent *** " + event.toString() +
" position=" + status.getRocketPosition() + " velocity=" + status.getRocketVelocity());
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
FlightDataBranch data = status.getFlightData();
System.out.printf("*** stepTaken *** time=%.3f position=" + status.getRocketPosition() +
" velocity=" + status.getRocketVelocity() + "=%.3f\n", status.getSimulationTime(), status.getRocketVelocity().length());
System.out.printf(" thrust=%.3fN drag==%.3fN mass=%.3fkg " +
"accZ=%.3fm/s2 acc=%.3fm/s2\n",
data.getLast(FlightDataType.TYPE_THRUST_FORCE),
data.getLast(FlightDataType.TYPE_DRAG_FORCE),
data.getLast(FlightDataType.TYPE_MASS),
data.getLast(FlightDataType.TYPE_ACCELERATION_Z),
data.getLast(FlightDataType.TYPE_ACCELERATION_TOTAL));
}
}

View File

@ -1,114 +0,0 @@
package net.sf.openrocket.simulation.listeners.example;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.MathUtil;
/**
* An example listener that applies a PI-controller to adjust the cant of fins
* named "CONTROL" to stop the rocket from rolling.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class RollControlListener extends AbstractSimulationListener {
// Name of control fin set
private static final String CONTROL_FIN_NAME = "CONTROL";
// Define custom flight data type
private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", "\u03B1fc", UnitGroup.UNITS_ANGLE);
// Simulation time at which PID controller is activated
private static final double START_TIME = 0.5;
// Desired roll rate (rad/sec)
private static final double SETPOINT = 0.0;
// Maximum control fin turn rate (rad/sec)
private static final double TURNRATE = 10 * Math.PI / 180;
// Maximum control fin angle (rad)
private static final double MAX_ANGLE = 15 * Math.PI / 180;
/*
* PID parameters
*
* At M=0.3 KP oscillation threshold between 0.35 and 0.4. Good KI=3
* At M=0.6 KP oscillation threshold between 0.07 and 0.08 Good KI=2
* At M=0.9 KP oscillation threshold between 0.013 and 0.014 Good KI=0.5
*/
private static final double KP = 0.007;
private static final double KI = 0.2;
private double rollRate;
private double prevTime = 0;
private double intState = 0;
private double finPosition = 0;
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) {
// Store the current roll rate for later use
rollRate = flightConditions.getRollRate();
return null;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
// Activate PID controller only after a specific time
if (status.getSimulationTime() < START_TIME) {
prevTime = status.getSimulationTime();
return;
}
// Find the fin set named CONTROL
FinSet finset = null;
for (RocketComponent c : status.getConfiguration().getActiveComponents()) {
if ((c instanceof FinSet) && (c.getName().equals(CONTROL_FIN_NAME))) {
finset = (FinSet) c;
break;
}
}
if (finset == null) {
throw new SimulationException("A fin set with name '" + CONTROL_FIN_NAME + "' was not found");
}
// Determine time step
double deltaT = status.getSimulationTime() - prevTime;
prevTime = status.getSimulationTime();
// PID controller
double error = SETPOINT - rollRate;
double p = KP * error;
intState += error * deltaT;
double i = KI * intState;
double value = p + i;
// Clamp the fin angle between -MAX_ANGLE and MAX_ANGLE
if (Math.abs(value) > MAX_ANGLE) {
System.err.printf("Attempting to set angle %.1f at t=%.3f, clamping.\n",
value * 180 / Math.PI, status.getSimulationTime());
value = MathUtil.clamp(value, -MAX_ANGLE, MAX_ANGLE);
}
// Limit the fin turn rate
if (finPosition < value) {
finPosition = Math.min(finPosition + TURNRATE * deltaT, value);
} else {
finPosition = Math.max(finPosition - TURNRATE * deltaT, value);
}
// Set the control fin cant and store the data
finset.setCantAngle(finPosition);
status.getFlightData().setValue(FIN_CANT_TYPE, finPosition);
}
}

View File

@ -1,65 +0,0 @@
package net.sf.openrocket.simulation.listeners.example;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
/**
* A simulation listener that stops the simulation after a specified number of steps or
* after a specified amount of simulation time.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class StopSimulationListener extends AbstractSimulationListener {
private final int REPORT = 500;
private final double stopTime;
private final int stopStep;
private int step = 0;
private long startTime = -1;
private long time = -1;
public StopSimulationListener() {
this(0, 0);
}
public StopSimulationListener(double t, int n) {
stopTime = t;
stopStep = n;
}
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
if (event.getType() == FlightEvent.Type.LAUNCH) {
System.out.println("Simulation starting.");
time = System.nanoTime();
startTime = System.nanoTime();
}
return true;
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
step++;
if ((step % REPORT) == 0) {
long t = System.nanoTime();
System.out.printf("Step %4d, time=%.3f, took %d us/step (avg. %d us/step)\n",
step, status.getSimulationTime(), (t - time) / 1000 / REPORT, (t - startTime) / 1000 / step);
time = t;
}
if (status.getSimulationTime() >= stopTime || step >= stopStep) {
System.out.printf("Stopping simulation, step=%d time=%.3f\n", step, status.getSimulationTime());
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END,
status.getSimulationTime(), null));
}
}
}

View File

@ -197,12 +197,12 @@ class SimulationOptionsPanel extends JPanel {
final JButton addExtension = new SelectColorButton(trans.get("simedtdlg.SimExt.add"));
this.extensionMenu = getExtensionMenu();
extensionMenu = getExtensionMenu();
addExtension.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
extensionMenu.show(addExtension, 5, addExtension.getBounds().height);
}
});
public void actionPerformed(ActionEvent ev) {
extensionMenu.show(addExtension, 5, addExtension.getBounds().height);
}
});
sub.add(addExtension, "growx, wrap 0");
currentExtensions = new JPanel(new MigLayout("fillx, gap 0 0, ins 0"));

View File

@ -1,4 +1,4 @@
package net.sf.openrocket.simulation.extension.impl;
package net.sf.openrocket.simulation.extension.example;
import javax.swing.JComponent;
import javax.swing.JLabel;

View File

@ -0,0 +1,83 @@
package net.sf.openrocket.simulation.extension.example;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.unit.UnitGroup;
@Plugin
public class RollControlConfigurator extends AbstractSwingSimulationExtensionConfigurator<RollControl> {
private JPanel panel;
private RollControl extension;
public RollControlConfigurator() {
super(RollControl.class);
}
@Override
protected JComponent getConfigurationComponent(RollControl extension, Simulation simulation, JPanel panel) {
this.panel = panel;
this.extension = extension;
panel.add(new JLabel("Control FinSet Name:"));
JTextField finSetName = new JTextField();
finSetName.setText(extension.getControlFinName());
finSetName.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {}
@Override
public void focusLost(FocusEvent e) {
extension.setControlFinName(finSetName.getText());
}
});
panel.add(finSetName, "growx, span3, wrap");
addRow("Start Time", "StartTime", UnitGroup.UNITS_SHORT_TIME, 0.0, 1200.0);
addRow("Desired Roll Rate", "SetPoint", UnitGroup.UNITS_ROLL, -10.0, 10.0);
addRow("Max Fin Turn Rate", "FinRate", UnitGroup.UNITS_ROLL, 0, 10);
addRow("Max Fin Angle", "MaxFinAngle", UnitGroup.UNITS_ANGLE, 0, 0.25);
panel.add(new JLabel("PID parameters:"), "newline 0.25in, span4, wrap");
panel.add(new JLabel("At M=0.3 KP oscillation threshold between 0.35 and 0.4. Good KI=3" ), "gapbefore indent, span4, wrap");
panel.add(new JLabel("At M=0.6 KP oscillation threshold between 0.07 and 0.08 Good KI=2" ), "gapbefore indent, span4, wrap");
panel.add(new JLabel("At M=0.9 KP oscillation threshold between 0.013 and 0.014 Good KI=0.5"), "gapbefore indent, span4, wrap");
addRow("KP (Proportional)", "KP", UnitGroup.UNITS_COEFFICIENT, 0.0, 0.02);
addRow("KI (Integrated)", "KI", UnitGroup.UNITS_COEFFICIENT, 0.0, 1.0);
return panel;
}
private void addRow(String prompt, String fieldName, UnitGroup units, double min, double max) {
panel.add(new JLabel(prompt + ":"));
DoubleModel m = new DoubleModel(extension, fieldName, units, min, max);
JSpinner spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "w 65lp!");
UnitSelector unit = new UnitSelector(m);
panel.add(unit, "w 25");
BasicSlider slider = new BasicSlider(m.getSliderModel(0, 1000));
panel.add(slider, "w 75lp, wrap");
}
}

View File

@ -0,0 +1,52 @@
package net.sf.openrocket.simulation.extension.example;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.unit.UnitGroup;
@Plugin
public class StopSimulationConfigurator extends AbstractSwingSimulationExtensionConfigurator<StopSimulation> {
private JPanel panel;
private StopSimulation extension;
public StopSimulationConfigurator() {
super(StopSimulation.class);
}
@Override
protected JComponent getConfigurationComponent(StopSimulation extension, Simulation simulation, JPanel panel) {
this.panel = panel;
this.extension = extension;
addRow("Report Rate", "ReportRate", 0, 1000);
addRow("Stop Step", "StopStep", 0, 50000);
addRow("Stop Time", "StopTime", 0, 1000);
return panel;
}
private void addRow(String prompt, String fieldName, int min, int max) {
panel.add(new JLabel(prompt + ":"));
IntegerModel m = new IntegerModel(extension, fieldName, min, max);
JSpinner spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "w 65lp!");
BasicSlider slider = new BasicSlider(m.getSliderModel());
panel.add(slider, "w 75lp, wrap");
}
}

View File

@ -123,6 +123,7 @@ public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfi
@Override
protected void close() {
util.setTrustedScript(extension.getLanguage(), extension.getScript(), trusted.isSelected());
super.close();
}
private void setLanguage(String language) {