Implement feature to compute the optimum delay for a simulation.

This commit is contained in:
kruland2607 2014-04-08 13:09:38 -05:00
parent 4565cdcd86
commit a34d41d6af
30 changed files with 487 additions and 189 deletions

View File

@ -430,6 +430,8 @@ simpanel.col.Motors = Motors
simpanel.col.Configuration = Configuration simpanel.col.Configuration = Configuration
simpanel.col.Velocityoffrod = Velocity off rod simpanel.col.Velocityoffrod = Velocity off rod
simpanel.col.Velocityatdeploy = Velocity at deployment simpanel.col.Velocityatdeploy = Velocity at deployment
simpanel.col.OptimumCoastTime = Optimum delay
simpanel.col.OptimumCoastTime.ttip = The time between last motor burnout and maximum possible altitude.
simpanel.col.Apogee = Apogee simpanel.col.Apogee = Apogee
simpanel.col.Maxvelocity = Max. velocity simpanel.col.Maxvelocity = Max. velocity
simpanel.col.Maxacceleration = Max. acceleration simpanel.col.Maxacceleration = Max. acceleration

View File

@ -536,6 +536,7 @@ public class OpenRocketSaver extends RocketSaver {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<databranch name=\""); sb.append("<databranch name=\"");
sb.append(TextUtil.escapeXML(branch.getBranchName())); sb.append(TextUtil.escapeXML(branch.getBranchName()));
sb.append("\" ");
// Kevins version where typekeys are used // Kevins version where typekeys are used
/* /*
@ -547,7 +548,19 @@ public class OpenRocketSaver extends RocketSaver {
} }
*/ */
sb.append("\" types=\""); if (!Double.isNaN(branch.getOptimumAltitude())) {
sb.append("optimumAltitude=\"");
sb.append(branch.getOptimumAltitude());
sb.append("\" ");
}
if (!Double.isNaN(branch.getTimeToOptimumAltitude())) {
sb.append("timeToOptimumAltitude=\"");
sb.append(branch.getTimeToOptimumAltitude());
sb.append("\" ");
}
sb.append("types=\"");
for (int i = 0; i < types.length; i++) { for (int i = 0; i < types.length; i++) {
if (i > 0) if (i > 0)
sb.append(","); sb.append(",");
@ -590,8 +603,6 @@ public class OpenRocketSaver extends RocketSaver {
writeln("</databranch>"); writeln("</databranch>");
} }
/* TODO: LOW: This is largely duplicated from above! */ /* TODO: LOW: This is largely duplicated from above! */
private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) { private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
int count = 0; int count = 0;

View File

@ -42,6 +42,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
branch = new FlightDataBranch(name, types); branch = new FlightDataBranch(name, types);
} }
/**
* @param timeToOptimumAltitude
* @see net.sf.openrocket.simulation.FlightDataBranch#setTimeToOptimumAltitude(double)
*/
public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
branch.setTimeToOptimumAltitude(timeToOptimumAltitude);
}
/**
* @param optimumAltitude
* @see net.sf.openrocket.simulation.FlightDataBranch#setOptimumAltitude(double)
*/
public void setOptimumAltitude(double optimumAltitude) {
branch.setOptimumAltitude(optimumAltitude);
}
// Find the full flight data type given name only // Find the full flight data type given name only
// Note: this way of doing it requires that custom expressions always come before flight data in the file, // Note: this way of doing it requires that custom expressions always come before flight data in the file,
// not the nicest but this is always the case anyway. // not the nicest but this is always the case anyway.

View File

@ -48,6 +48,23 @@ class FlightDataHandler extends AbstractElementHandler {
dataHandler = new FlightDataBranchHandler(attributes.get("name"), dataHandler = new FlightDataBranchHandler(attributes.get("name"),
attributes.get("types"), attributes.get("types"),
simHandler, context); simHandler, context);
if (attributes.get("optimumAltitude") != null) {
double optimumAltitude = Double.NaN;
try {
optimumAltitude = Double.parseDouble(attributes.get("optimumAltitude"));
} catch (NumberFormatException ignore) {
}
dataHandler.setOptimumAltitude(optimumAltitude);
}
if (attributes.get("timeToOptimumAltitude") != null) {
double timeToOptimumAltitude = Double.NaN;
try {
timeToOptimumAltitude = Double.parseDouble(attributes.get("timeToOptimumAltitude"));
} catch (NumberFormatException ignore) {
}
dataHandler.setTimeToOptimumAltitude(timeToOptimumAltitude);
}
return dataHandler; return dataHandler;
} }

View File

@ -21,6 +21,7 @@ import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationLaunchException; import net.sf.openrocket.simulation.exception.SimulationLaunchException;
import net.sf.openrocket.simulation.listeners.SimulationListenerHelper; import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
import net.sf.openrocket.simulation.listeners.system.OptimumCoastListener;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
@ -120,8 +121,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = status.getRocketVelocity(); Coordinate originVelocity = status.getRocketVelocity();
try { try {
double maxAlt = Double.NEGATIVE_INFINITY;
// Start the simulation // Start the simulation
while (handleEvents()) { while (handleEvents()) {
@ -149,8 +148,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getConfiguration().getRocket(), status.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, status.getRocketPosition().z))); new Pair<Double, Double>(oldAlt, status.getRocketPosition().z)));
if (status.getRocketPosition().z > maxAlt) { if (status.getRocketPosition().z > status.getMaxAlt()) {
maxAlt = status.getRocketPosition().z; status.setMaxAlt(status.getRocketPosition().z);
} }
@ -189,7 +188,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for apogee // Check for apogee
if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) { if (!status.isApogeeReached() && status.getRocketPosition().z < status.getMaxAlt() - 0.01) {
status.setMaxAltTime(status.getSimulationTime());
addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(), addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(),
status.getConfiguration().getRocket())); status.getConfiguration().getRocket()));
} }
@ -464,6 +464,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Mark apogee as reached // Mark apogee as reached
status.setApogeeReached(true); status.setApogeeReached(true);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
// This apogee event might be the optimum if recovery has not already happened.
if (status.getSimulationConditions().isCalculateExtras() && status.getDeployedRecoveryDevices().size() == 0) {
status.getFlightData().setOptimumAltitude(status.getMaxAlt());
status.getFlightData().setTimeToOptimumAltitude(status.getMaxAltTime());
}
break; break;
case RECOVERY_DEVICE_DEPLOYMENT: case RECOVERY_DEVICE_DEPLOYMENT:
@ -501,6 +506,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.setLiftoff(true); status.setLiftoff(true);
status.getDeployedRecoveryDevices().add((RecoveryDevice) c); status.getDeployedRecoveryDevices().add((RecoveryDevice) c);
// If we haven't already reached apogee, then we need to compute the actual coast time
// to determine the optimum altitude.
if (status.getSimulationConditions().isCalculateExtras() && !status.isApogeeReached()) {
FlightData coastStatus = computeCoastTime();
status.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude());
status.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee());
}
this.currentStepper = this.landingStepper; this.currentStepper = this.landingStepper;
this.status = currentStepper.initialize(status); this.status = currentStepper.initialize(status);
@ -601,5 +614,17 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
} }
private FlightData computeCoastTime() {
try {
SimulationConditions conds = status.getSimulationConditions().clone();
conds.getSimulationListenerList().add(OptimumCoastListener.INSTANCE);
BasicEventSimulationEngine e = new BasicEventSimulationEngine();
FlightData d = e.simulate(conds);
return d;
} catch (Exception e) {
log.warn("Exception computing coast time: ", e);
return null;
}
}
} }

View File

@ -36,7 +36,15 @@ public class FlightDataBranch implements Monitorable {
private final Map<FlightDataType, Double> maxValues = new HashMap<FlightDataType, Double>(); private final Map<FlightDataType, Double> maxValues = new HashMap<FlightDataType, Double>();
private final Map<FlightDataType, Double> minValues = new HashMap<FlightDataType, Double>(); private final Map<FlightDataType, Double> minValues = new HashMap<FlightDataType, Double>();
/**
* time for the rocket to reach apogee if the flight had been no recovery deployment
*/
private double timeToOptimumAltitude = Double.NaN;
/**
* Altitude the rocket would reach if there had been no recovery deployment.
*/
private double optimumAltitude = Double.NaN;
private final ArrayList<FlightEvent> events = new ArrayList<FlightEvent>(); private final ArrayList<FlightEvent> events = new ArrayList<FlightEvent>();
private Mutable mutable = new Mutable(); private Mutable mutable = new Mutable();
@ -73,12 +81,12 @@ public class FlightDataBranch implements Monitorable {
*/ */
public FlightDataBranch() { public FlightDataBranch() {
branchName = "Empty branch"; branchName = "Empty branch";
for (FlightDataType type : FlightDataType.ALL_TYPES){ for (FlightDataType type : FlightDataType.ALL_TYPES) {
this.setValue(type, Double.NaN); this.setValue(type, Double.NaN);
} }
this.immute(); this.immute();
} }
/** /**
* Adds a new point into the data branch. The value for all types is set to NaN by default. * Adds a new point into the data branch. The value for all types is set to NaN by default.
* *
@ -115,10 +123,10 @@ public class FlightDataBranch implements Monitorable {
} }
values.put(type, list); values.put(type, list);
minValues.put(type, value); minValues.put(type, value);
maxValues.put(type, value); maxValues.put(type, value);
} }
if (list.size() > 0){ if (list.size() > 0) {
list.set(list.size() - 1, value); list.set(list.size() - 1, value);
} }
@ -219,6 +227,50 @@ public class FlightDataBranch implements Monitorable {
} }
/**
* @return the timeToOptimumAltitude
*/
public double getTimeToOptimumAltitude() {
return timeToOptimumAltitude;
}
/**
* @param timeToOptimumAltitude the timeToOptimumAltitude to set
*/
public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
this.timeToOptimumAltitude = timeToOptimumAltitude;
}
/**
* @return the optimumAltitude
*/
public double getOptimumAltitude() {
return optimumAltitude;
}
/**
* @param optimumAltitude the optimumAltitude to set
*/
public void setOptimumAltitude(double optimumAltitude) {
this.optimumAltitude = optimumAltitude;
}
public double getOptimumDelay() {
if (Double.isNaN(timeToOptimumAltitude)) {
return Double.NaN;
}
// TODO - we really want the first burnout of this stage. which
// could be computed as the first burnout after the last stage separation event.
// however, that's not quite so concise
FlightEvent e = getLastEvent(FlightEvent.Type.BURNOUT);
if (e != null) {
return timeToOptimumAltitude - e.getTime();
}
return Double.NaN;
}
/** /**
* Add a flight event to this branch. * Add a flight event to this branch.
* *
@ -241,6 +293,34 @@ public class FlightDataBranch implements Monitorable {
return events.clone(); return events.clone();
} }
/**
* Return the first event of the given type.
* @param type
* @return
*/
public FlightEvent getFirstEvent(FlightEvent.Type type) {
for (FlightEvent e : events) {
if (e.getType() == type) {
return e;
}
}
return null;
}
/**
* Return the last event of the given type.
* @param type
* @return
*/
public FlightEvent getLastEvent(FlightEvent.Type type) {
FlightEvent retval = null;
for (FlightEvent e : events) {
if (e.getType() == type) {
retval = e;
}
}
return retval;
}
/** /**
* Make this FlightDataBranch immutable. Any calls to the set methods that would * Make this FlightDataBranch immutable. Any calls to the set methods that would

View File

@ -29,7 +29,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
private String motorID = null; private String motorID = null;
private Simulation simulation; // The parent simulation private Simulation simulation; // The parent simulation
private double launchRodLength = 1; private double launchRodLength = 1;
/** Launch rod angle >= 0, radians from vertical */ /** Launch rod angle >= 0, radians from vertical */
@ -45,7 +45,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
private WorldCoordinate launchSite = new WorldCoordinate(0, 0, 0); private WorldCoordinate launchSite = new WorldCoordinate(0, 0, 0);
private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL; private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
private WindModel windModel; private WindModel windModel;
private AtmosphericModel atmosphericModel; private AtmosphericModel atmosphericModel;
private GravityModel gravityModel; private GravityModel gravityModel;
@ -53,25 +53,25 @@ public class SimulationConditions implements Monitorable, Cloneable {
private AerodynamicCalculator aerodynamicCalculator; private AerodynamicCalculator aerodynamicCalculator;
private MassCalculator massCalculator; private MassCalculator massCalculator;
private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP; private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
private double maximumAngleStep = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP; private double maximumAngleStep = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
/* Whether to calculate additional data or only primary simulation figures */ /* Whether to calculate additional data or only primary simulation figures */
private boolean calculateExtras = true; private boolean calculateExtras = true;
private List<SimulationListener> simulationListeners = new ArrayList<SimulationListener>(); private List<SimulationListener> simulationListeners = new ArrayList<SimulationListener>();
private int randomSeed = 0; private int randomSeed = 0;
private int modID = 0; private int modID = 0;
private int modIDadd = 0; private int modIDadd = 0;
public AerodynamicCalculator getAerodynamicCalculator() { public AerodynamicCalculator getAerodynamicCalculator() {
return aerodynamicCalculator; return aerodynamicCalculator;
} }
@ -253,7 +253,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
} }
public int getRandomSeed() { public int getRandomSeed() {
return randomSeed; return randomSeed;
} }
@ -267,8 +267,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
public void setSimulation(Simulation sim) { public void setSimulation(Simulation sim) {
this.simulation = sim; this.simulation = sim;
} }
public Simulation getSimulation(){ public Simulation getSimulation() {
return this.simulation; return this.simulation;
} }
@ -291,7 +291,12 @@ public class SimulationConditions implements Monitorable, Cloneable {
public SimulationConditions clone() { public SimulationConditions clone() {
try { try {
// TODO: HIGH: Deep clone models // TODO: HIGH: Deep clone models
return (SimulationConditions) super.clone(); SimulationConditions clone = (SimulationConditions) super.clone();
clone.simulationListeners = new ArrayList<SimulationListener>(this.simulationListeners.size());
for (SimulationListener listener : this.simulationListeners) {
clone.simulationListeners.add(listener.clone());
}
return clone;
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
throw new BugException(e); throw new BugException(e);
} }

View File

@ -27,10 +27,6 @@ import net.sf.openrocket.util.WorldCoordinate;
*/ */
public class SimulationStatus implements Monitorable { public class SimulationStatus implements Monitorable {
/*
* NOTE! All fields must be added to copyFrom() method!!
*/
private SimulationConditions simulationConditions; private SimulationConditions simulationConditions;
private Configuration configuration; private Configuration configuration;
private MotorInstanceConfiguration motorConfiguration; private MotorInstanceConfiguration motorConfiguration;
@ -51,12 +47,12 @@ public class SimulationStatus implements Monitorable {
// Set of burnt out motors // Set of burnt out motors
Set<MotorId> motorBurntOut = new HashSet<MotorId>(); Set<MotorId> motorBurntOut = new HashSet<MotorId>();
/** Nanosecond time when the simulation was started. */ /** Nanosecond time when the simulation was started. */
private long simulationStartWallTime = Long.MIN_VALUE; private long simulationStartWallTime = Long.MIN_VALUE;
/** Set to true when a motor has ignited. */ /** Set to true when a motor has ignited. */
private boolean motorIgnited = false; private boolean motorIgnited = false;
@ -83,38 +79,40 @@ public class SimulationStatus implements Monitorable {
/** Available for special purposes by the listeners. */ /** Available for special purposes by the listeners. */
private final Map<String, Object> extraData = new HashMap<String, Object>(); private final Map<String, Object> extraData = new HashMap<String, Object>();
double maxAlt = Double.NEGATIVE_INFINITY;
double maxAltTime = 0;
private int modID = 0; private int modID = 0;
private int modIDadd = 0; private int modIDadd = 0;
public SimulationStatus( Configuration configuration, public SimulationStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration, MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions ) { SimulationConditions simulationConditions) {
this.simulationConditions = simulationConditions; this.simulationConditions = simulationConditions;
this.configuration = configuration; this.configuration = configuration;
this.motorConfiguration = motorConfiguration; this.motorConfiguration = motorConfiguration;
this.time = 0; this.time = 0;
this.previousTimeStep = this.simulationConditions.getTimeStep(); this.previousTimeStep = this.simulationConditions.getTimeStep();
this.position = Coordinate.NUL; this.position = Coordinate.NUL;
this.velocity = Coordinate.NUL; this.velocity = Coordinate.NUL;
this.worldPosition = this.simulationConditions.getLaunchSite(); this.worldPosition = this.simulationConditions.getLaunchSite();
// Initialize to roll angle with least stability w.r.t. the wind // Initialize to roll angle with least stability w.r.t. the wind
Quaternion o; Quaternion o;
FlightConditions cond = new FlightConditions(this.configuration); FlightConditions cond = new FlightConditions(this.configuration);
this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null); this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null);
double angle = -cond.getTheta() - this.simulationConditions.getLaunchRodDirection(); double angle = -cond.getTheta() - this.simulationConditions.getLaunchRodDirection();
o = Quaternion.rotation(new Coordinate(0, 0, angle)); o = Quaternion.rotation(new Coordinate(0, 0, angle));
// Launch rod angle and direction // Launch rod angle and direction
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, this.simulationConditions.getLaunchRodAngle(), 0))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, this.simulationConditions.getLaunchRodAngle(), 0)));
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, this.simulationConditions.getLaunchRodDirection()))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, this.simulationConditions.getLaunchRodDirection())));
this.orientation = o; this.orientation = o;
this.rotationVelocity = Coordinate.NUL; this.rotationVelocity = Coordinate.NUL;
/* /*
* Calculate the effective launch rod length taking into account launch lugs. * Calculate the effective launch rod length taking into account launch lugs.
* If no lugs are found, assume a tower launcher of full length. * If no lugs are found, assume a tower launcher of full length.
@ -140,16 +138,16 @@ public class SimulationStatus implements Monitorable {
} }
} }
this.effectiveLaunchRodLength = length; this.effectiveLaunchRodLength = length;
this.simulationStartWallTime = System.nanoTime(); this.simulationStartWallTime = System.nanoTime();
this.motorIgnited = false; this.motorIgnited = false;
this.liftoff = false; this.liftoff = false;
this.launchRodCleared = false; this.launchRodCleared = false;
this.apogeeReached = false; this.apogeeReached = false;
this.warnings = new WarningSet(); this.warnings = new WarningSet();
} }
/** /**
@ -163,7 +161,7 @@ public class SimulationStatus implements Monitorable {
* *
* @param orig the object from which to copy * @param orig the object from which to copy
*/ */
public SimulationStatus( SimulationStatus orig ) { public SimulationStatus(SimulationStatus orig) {
this.simulationConditions = orig.simulationConditions.clone(); this.simulationConditions = orig.simulationConditions.clone();
this.configuration = orig.configuration.clone(); this.configuration = orig.configuration.clone();
this.motorConfiguration = orig.motorConfiguration.clone(); this.motorConfiguration = orig.motorConfiguration.clone();
@ -292,11 +290,11 @@ public class SimulationStatus implements Monitorable {
} }
public boolean addBurntOutMotor( MotorId motor ) { public boolean addBurntOutMotor(MotorId motor) {
return motorBurntOut.add(motor); return motorBurntOut.add(motor);
} }
public Quaternion getRocketOrientationQuaternion() { public Quaternion getRocketOrientationQuaternion() {
return orientation; return orientation;
} }
@ -384,7 +382,7 @@ public class SimulationStatus implements Monitorable {
} }
public void setTumbling( boolean tumbling ) { public void setTumbling(boolean tumbling) {
this.tumbling = tumbling; this.tumbling = tumbling;
this.modID++; this.modID++;
} }
@ -393,6 +391,24 @@ public class SimulationStatus implements Monitorable {
return tumbling; return tumbling;
} }
public double getMaxAlt() {
return maxAlt;
}
public void setMaxAlt(double maxAlt) {
this.maxAlt = maxAlt;
this.modID++;
}
public double getMaxAltTime() {
return maxAltTime;
}
public void setMaxAltTime(double maxAltTime) {
this.maxAltTime = maxAltTime;
this.modID++;
}
public Set<RecoveryDevice> getDeployedRecoveryDevices() { public Set<RecoveryDevice> getDeployedRecoveryDevices() {
return deployedRecoveryDevices; return deployedRecoveryDevices;
} }
@ -477,5 +493,5 @@ public class SimulationStatus implements Monitorable {
eventQueue.getModID() + warnings.getModID()); eventQueue.getModID() + warnings.getModID());
} }
} }

View File

@ -72,7 +72,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
* Return an array of any flight data types this listener creates. * Return an array of any flight data types this listener creates.
*/ */
@Override @Override
public FlightDataType[] getFlightDataTypes(){ public FlightDataType[] getFlightDataTypes() {
return new FlightDataType[] {}; return new FlightDataType[] {};
} }
@ -183,4 +183,9 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
return null; return null;
} }
@Override
public AbstractSimulationListener clone() throws CloneNotSupportedException {
return (AbstractSimulationListener) super.clone();
}
} }

View File

@ -4,9 +4,13 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
/**
* Listen to simulation events and possibly take action.
public interface SimulationListener { *
* If the implementation maintains any state, it should be properly cloned.
*
*/
public interface SimulationListener extends Cloneable {
/** /**
* Get the name of this simulation listener. Ideally this should be localized, as * Get the name of this simulation listener. Ideally this should be localized, as
@ -83,5 +87,5 @@ public interface SimulationListener {
*/ */
public FlightDataType[] getFlightDataTypes(); public FlightDataType[] getFlightDataTypes();
public SimulationListener clone() throws CloneNotSupportedException;
} }

View File

@ -0,0 +1,38 @@
package net.sf.openrocket.simulation.listeners.system;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
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;
/**
* Simulation listener which ignores recovery deployment events and ends the simulation
* when apogee is reached.
*
* @author kevin
*
*/
public class OptimumCoastListener extends AbstractSimulationListener {
public static final OptimumCoastListener INSTANCE = new OptimumCoastListener();
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
if (event.getType() == FlightEvent.Type.APOGEE) {
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime()));
}
return true;
}
@Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
return false;
}
@Override
public boolean isSystemListener() {
return true;
}
}

View File

@ -6,6 +6,7 @@ import javax.swing.table.TableColumnModel;
public abstract class Column { public abstract class Column {
private final String name; private final String name;
private final String toolTip;
/** /**
* Create a new column with specified name. Additionally, the {@link #getValueAt(int)} * Create a new column with specified name. Additionally, the {@link #getValueAt(int)}
@ -15,6 +16,17 @@ public abstract class Column {
*/ */
public Column(String name) { public Column(String name) {
this.name = name; this.name = name;
this.toolTip = null;
}
/**
* Create a new column with specified name and toolTip.
*
*
*/
public Column(String name, String toolTip ) {
this.name = name;
this.toolTip = toolTip;
} }
/** /**
@ -87,5 +99,9 @@ public abstract class Column {
public Comparator getComparator() { public Comparator getComparator() {
return null; return null;
} }
public String getToolTip() {
return toolTip;
}
} }

View File

@ -0,0 +1,28 @@
package net.sf.openrocket.gui.adaptors;
import java.awt.event.MouseEvent;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;
public class ColumnTable extends JTable {
public ColumnTable( ColumnTableModel model ) {
super(model);
}
@Override
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader( columnModel ) {
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
tip = ((ColumnTableModel) getModel()).getColumn(realIndex).getToolTip();
return tip;
}
};
}
}

View File

@ -15,6 +15,11 @@ public abstract class ValueColumn extends Column {
this.unit = unit; this.unit = unit;
} }
public ValueColumn( String name, String toolTip, UnitGroup unit ) {
super( name, toolTip );
this.unit = unit;
}
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
Double d = valueAt(row); Double d = valueAt(row);

View File

@ -43,6 +43,7 @@ import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel; import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.FlightConfigurationModel; import net.sf.openrocket.gui.adaptors.FlightConfigurationModel;
@ -243,7 +244,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
} }
}; };
table = new JTable(cpTableModel); table = new ColumnTable(cpTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY); table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK); table.setSelectionForeground(Color.BLACK);

View File

@ -40,6 +40,7 @@ import javax.swing.table.TableRowSorter;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel; import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.SelectableLabel; import net.sf.openrocket.gui.components.SelectableLabel;
import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.GUIUtil;
@ -266,7 +267,7 @@ public class DebugLogDialog extends JDialog {
} }
}; };
table = new JTable(model); table = new ColumnTable(model);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY); table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK); table.setSelectionForeground(Color.BLACK);

View File

@ -23,6 +23,7 @@ import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.database.Database; import net.sf.openrocket.database.Database;
import net.sf.openrocket.database.Databases; import net.sf.openrocket.database.Databases;
import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel; import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.components.StyledLabel.Style;
@ -108,7 +109,7 @@ public class MaterialEditPanel extends JPanel {
} }
}; };
table = new JTable(model); table = new ColumnTable(model);
model.setColumnWidths(table.getColumnModel()); model.setColumnWidths(table.getColumnModel());
table.setAutoCreateRowSorter(true); table.setAutoCreateRowSorter(true);
table.setDefaultRenderer(Object.class, new MaterialCellRenderer()); table.setDefaultRenderer(Object.class, new MaterialCellRenderer());

View File

@ -35,6 +35,7 @@ import net.sf.openrocket.document.events.DocumentChangeListener;
import net.sf.openrocket.document.events.SimulationChangeEvent; import net.sf.openrocket.document.events.SimulationChangeEvent;
import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel; import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.ColumnTableRowSorter; import net.sf.openrocket.gui.adaptors.ColumnTableRowSorter;
import net.sf.openrocket.gui.adaptors.ValueColumn; import net.sf.openrocket.gui.adaptors.ValueColumn;
@ -48,6 +49,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.unit.UnitGroup;
@ -59,37 +61,37 @@ import org.slf4j.LoggerFactory;
public class SimulationPanel extends JPanel { public class SimulationPanel extends JPanel {
private static final Logger log = LoggerFactory.getLogger(SimulationPanel.class); private static final Logger log = LoggerFactory.getLogger(SimulationPanel.class);
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private static final Color WARNING_COLOR = Color.RED; private static final Color WARNING_COLOR = Color.RED;
private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
private static final Color OK_COLOR = new Color(60, 150, 0); private static final Color OK_COLOR = new Color(60, 150, 0);
private static final String OK_TEXT = "\u2714"; // Heavy check mark private static final String OK_TEXT = "\u2714"; // Heavy check mark
private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
private final OpenRocketDocument document; private final OpenRocketDocument document;
private final ColumnTableModel simulationTableModel; private final ColumnTableModel simulationTableModel;
private final JTable simulationTable; private final JTable simulationTable;
private final JButton editButton; private final JButton editButton;
private final JButton runButton; private final JButton runButton;
private final JButton deleteButton; private final JButton deleteButton;
private final JButton plotButton; private final JButton plotButton;
public SimulationPanel(OpenRocketDocument doc) { public SimulationPanel(OpenRocketDocument doc) {
super(new MigLayout("fill", "[grow][][][][][][grow]")); super(new MigLayout("fill", "[grow][][][][][][grow]"));
this.document = doc; this.document = doc;
//////// The simulation action buttons //////// The simulation action buttons
//// New simulation button //// New simulation button
{ {
JButton button = new JButton(trans.get("simpanel.but.newsimulation")); JButton button = new JButton(trans.get("simpanel.but.newsimulation"));
@ -100,19 +102,19 @@ public class SimulationPanel extends JPanel {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
Simulation sim = new Simulation(document.getRocket()); Simulation sim = new Simulation(document.getRocket());
sim.setName(document.getNextSimulationName()); sim.setName(document.getNextSimulationName());
int n = document.getSimulationCount(); int n = document.getSimulationCount();
document.addSimulation(sim); document.addSimulation(sim);
simulationTableModel.fireTableDataChanged(); simulationTableModel.fireTableDataChanged();
simulationTable.clearSelection(); simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(n, n); simulationTable.addRowSelectionInterval(n, n);
openDialog(false, sim); openDialog(false, sim);
} }
}); });
this.add(button, "skip 1, gapright para"); this.add(button, "skip 1, gapright para");
} }
//// Edit simulation button //// Edit simulation button
editButton = new JButton(trans.get("simpanel.but.editsimulation")); editButton = new JButton(trans.get("simpanel.but.editsimulation"));
//// Edit the selected simulation //// Edit the selected simulation
@ -133,7 +135,7 @@ public class SimulationPanel extends JPanel {
} }
}); });
this.add(editButton, "gapright para"); this.add(editButton, "gapright para");
//// Run simulations //// Run simulations
runButton = new JButton(trans.get("simpanel.but.runsimulations")); runButton = new JButton(trans.get("simpanel.but.runsimulations"));
//// Re-run the selected simulations //// Re-run the selected simulations
@ -150,7 +152,7 @@ public class SimulationPanel extends JPanel {
selection[i] = simulationTable.convertRowIndexToModel(selection[i]); selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
sims[i] = document.getSimulation(selection[i]); sims[i] = document.getSimulation(selection[i]);
} }
long t = System.currentTimeMillis(); long t = System.currentTimeMillis();
new SimulationRunDialog(SwingUtilities.getWindowAncestor( new SimulationRunDialog(SwingUtilities.getWindowAncestor(
SimulationPanel.this), document, sims).setVisible(true); SimulationPanel.this), document, sims).setVisible(true);
@ -159,7 +161,7 @@ public class SimulationPanel extends JPanel {
} }
}); });
this.add(runButton, "gapright para"); this.add(runButton, "gapright para");
//// Delete simulations button //// Delete simulations button
deleteButton = new JButton(trans.get("simpanel.but.deletesimulations")); deleteButton = new JButton(trans.get("simpanel.but.deletesimulations"));
//// Delete the selected simulations //// Delete the selected simulations
@ -174,34 +176,34 @@ public class SimulationPanel extends JPanel {
// Verify deletion // Verify deletion
boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true); boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
if (verify) { if (verify) {
JPanel panel = new JPanel(new MigLayout()); JPanel panel = new JPanel(new MigLayout());
//// Do not ask me again //// Do not ask me again
JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask")); JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
panel.add(dontAsk, "wrap"); panel.add(dontAsk, "wrap");
//// You can change the default operation in the preferences. //// You can change the default operation in the preferences.
panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2)); panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
int ret = JOptionPane.showConfirmDialog(SimulationPanel.this, int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
new Object[] { new Object[] {
//// Delete the selected simulations? //// Delete the selected simulations?
trans.get("simpanel.dlg.lbl.DeleteSim1"), trans.get("simpanel.dlg.lbl.DeleteSim1"),
//// <html><i>This operation cannot be undone.</i> //// <html><i>This operation cannot be undone.</i>
trans.get("simpanel.dlg.lbl.DeleteSim2"), trans.get("simpanel.dlg.lbl.DeleteSim2"),
"", "",
panel }, panel },
//// Delete simulations //// Delete simulations
trans.get("simpanel.dlg.lbl.DeleteSim3"), trans.get("simpanel.dlg.lbl.DeleteSim3"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE); JOptionPane.WARNING_MESSAGE);
if (ret != JOptionPane.OK_OPTION) if (ret != JOptionPane.OK_OPTION)
return; return;
if (dontAsk.isSelected()) { if (dontAsk.isSelected()) {
Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false); Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
} }
} }
// Delete simulations // Delete simulations
for (int i = 0; i < selection.length; i++) { for (int i = 0; i < selection.length; i++) {
selection[i] = simulationTable.convertRowIndexToModel(selection[i]); selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
@ -214,7 +216,7 @@ public class SimulationPanel extends JPanel {
} }
}); });
this.add(deleteButton, "gapright para"); this.add(deleteButton, "gapright para");
//// Plot / export button //// Plot / export button
plotButton = new JButton(trans.get("simpanel.but.plotexport")); plotButton = new JButton(trans.get("simpanel.but.plotexport"));
// button = new JButton("Plot flight"); // button = new JButton("Plot flight");
@ -228,58 +230,58 @@ public class SimulationPanel extends JPanel {
selected = simulationTable.convertRowIndexToModel(selected); selected = simulationTable.convertRowIndexToModel(selected);
simulationTable.clearSelection(); simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(selected, selected); simulationTable.addRowSelectionInterval(selected, selected);
Simulation sim = document.getSimulations().get(selected); Simulation sim = document.getSimulations().get(selected);
if (!sim.hasSimulationData()) { if (!sim.hasSimulationData()) {
new SimulationRunDialog(SwingUtilities.getWindowAncestor( new SimulationRunDialog(SwingUtilities.getWindowAncestor(
SimulationPanel.this), document, sim).setVisible(true); SimulationPanel.this), document, sim).setVisible(true);
} }
fireMaintainSelection(); fireMaintainSelection();
openDialog(true, sim); openDialog(true, sim);
} }
}); });
this.add(plotButton, "wrap para"); this.add(plotButton, "wrap para");
//////// The simulation table //////// The simulation table
simulationTableModel = new ColumnTableModel( simulationTableModel = new ColumnTableModel(
//// Status and warning column //// Status and warning column
new Column("") { new Column("") {
private JLabel label = null; private JLabel label = null;
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
// Initialize the label // Initialize the label
if (label == null) { if (label == null) {
label = new StyledLabel(2f); label = new StyledLabel(2f);
label.setIconTextGap(1); label.setIconTextGap(1);
// label.setFont(label.getFont().deriveFont(Font.BOLD)); // label.setFont(label.getFont().deriveFont(Font.BOLD));
} }
// Set simulation status icon // Set simulation status icon
Simulation.Status status = document.getSimulation(row).getStatus(); Simulation.Status status = document.getSimulation(row).getStatus();
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status)); label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
// Set warning marker // Set warning marker
if (status == Simulation.Status.NOT_SIMULATED || if (status == Simulation.Status.NOT_SIMULATED ||
status == Simulation.Status.EXTERNAL) { status == Simulation.Status.EXTERNAL) {
label.setText(""); label.setText("");
} else { } else {
WarningSet w = document.getSimulation(row).getSimulatedWarnings(); WarningSet w = document.getSimulation(row).getSimulatedWarnings();
if (w == null) { if (w == null) {
label.setText(""); label.setText("");
@ -291,21 +293,21 @@ public class SimulationPanel extends JPanel {
label.setText(WARNING_TEXT); label.setText(WARNING_TEXT);
} }
} }
return label; return label;
} }
@Override @Override
public int getExactWidth() { public int getExactWidth() {
return 36; return 36;
} }
@Override @Override
public Class<?> getColumnClass() { public Class<?> getColumnClass() {
return JLabel.class; return JLabel.class;
} }
}, },
//// Simulation name //// Simulation name
//// Name //// Name
new Column(trans.get("simpanel.col.Name")) { new Column(trans.get("simpanel.col.Name")) {
@ -315,18 +317,18 @@ public class SimulationPanel extends JPanel {
return null; return null;
return document.getSimulation(row).getName(); return document.getSimulation(row).getName();
} }
@Override @Override
public int getDefaultWidth() { public int getDefaultWidth() {
return 125; return 125;
} }
@Override @Override
public Comparator getComparator() { public Comparator getComparator() {
return new AlphanumComparator(); return new AlphanumComparator();
} }
}, },
//// Simulation configuration //// Simulation configuration
new Column(trans.get("simpanel.col.Configuration")) { new Column(trans.get("simpanel.col.Configuration")) {
@Override @Override
@ -336,144 +338,165 @@ public class SimulationPanel extends JPanel {
Configuration c = document.getSimulation(row).getConfiguration(); Configuration c = document.getSimulation(row).getConfiguration();
return descriptor.format(c.getRocket(), c.getFlightConfigurationID()); return descriptor.format(c.getRocket(), c.getFlightConfigurationID());
} }
@Override @Override
public int getDefaultWidth() { public int getDefaultWidth() {
return 125; return 125;
} }
}, },
//// Launch rod velocity //// Launch rod velocity
new ValueColumn(trans.get("simpanel.col.Velocityoffrod"), UnitGroup.UNITS_VELOCITY) { new ValueColumn(trans.get("simpanel.col.Velocityoffrod"), UnitGroup.UNITS_VELOCITY) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getLaunchRodVelocity(); return data.getLaunchRodVelocity();
} }
}, },
//// Apogee //// Apogee
new ValueColumn(trans.get("simpanel.col.Apogee"), UnitGroup.UNITS_DISTANCE) { new ValueColumn(trans.get("simpanel.col.Apogee"), UnitGroup.UNITS_DISTANCE) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getMaxAltitude(); return data.getMaxAltitude();
} }
}, },
//// Velocity at deployment //// Velocity at deployment
new ValueColumn(trans.get("simpanel.col.Velocityatdeploy"), UnitGroup.UNITS_VELOCITY) { new ValueColumn(trans.get("simpanel.col.Velocityatdeploy"), UnitGroup.UNITS_VELOCITY) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getDeploymentVelocity(); return data.getDeploymentVelocity();
} }
}, },
//// Deployment Time from Apogee
new ValueColumn(trans.get("simpanel.col.OptimumCoastTime"),
trans.get("simpanel.col.OptimumCoastTime.ttip"),
UnitGroup.UNITS_SHORT_TIME) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null || data.getBranchCount() == 0)
return null;
double val = data.getBranch(0).getOptimumDelay();
if ( Double.isNaN(val) ) {
return null;
}
return val;
}
},
//// Maximum velocity //// Maximum velocity
new ValueColumn(trans.get("simpanel.col.Maxvelocity"), UnitGroup.UNITS_VELOCITY) { new ValueColumn(trans.get("simpanel.col.Maxvelocity"), UnitGroup.UNITS_VELOCITY) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getMaxVelocity(); return data.getMaxVelocity();
} }
}, },
//// Maximum acceleration //// Maximum acceleration
new ValueColumn(trans.get("simpanel.col.Maxacceleration"), UnitGroup.UNITS_ACCELERATION) { new ValueColumn(trans.get("simpanel.col.Maxacceleration"), UnitGroup.UNITS_ACCELERATION) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getMaxAcceleration(); return data.getMaxAcceleration();
} }
}, },
//// Time to apogee //// Time to apogee
new ValueColumn(trans.get("simpanel.col.Timetoapogee"), UnitGroup.UNITS_FLIGHT_TIME) { new ValueColumn(trans.get("simpanel.col.Timetoapogee"), UnitGroup.UNITS_FLIGHT_TIME) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getTimeToApogee(); return data.getTimeToApogee();
} }
}, },
//// Flight time //// Flight time
new ValueColumn(trans.get("simpanel.col.Flighttime"), UnitGroup.UNITS_FLIGHT_TIME) { new ValueColumn(trans.get("simpanel.col.Flighttime"), UnitGroup.UNITS_FLIGHT_TIME) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getFlightTime(); return data.getFlightTime();
} }
}, },
//// Ground hit velocity //// Ground hit velocity
new ValueColumn(trans.get("simpanel.col.Groundhitvelocity"), UnitGroup.UNITS_VELOCITY) { new ValueColumn(trans.get("simpanel.col.Groundhitvelocity"), UnitGroup.UNITS_VELOCITY) {
@Override @Override
public Double valueAt(int row) { public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return null; return null;
FlightData data = document.getSimulation(row).getSimulatedData(); FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null) if (data == null)
return null; return null;
return data.getGroundHitVelocity(); return data.getGroundHitVelocity();
} }
} }
) { ) {
@Override @Override
public int getRowCount() { public int getRowCount() {
return document.getSimulationCount(); return document.getSimulationCount();
} }
}; };
// Override processKeyBinding so that the JTable does not catch // Override processKeyBinding so that the JTable does not catch
// key bindings used in menu accelerators // key bindings used in menu accelerators
simulationTable = new JTable(simulationTableModel) { simulationTable = new ColumnTable(simulationTableModel) {
@Override @Override
protected boolean processKeyBinding(KeyStroke ks, protected boolean processKeyBinding(KeyStroke ks,
KeyEvent e, KeyEvent e,
@ -487,8 +510,8 @@ public class SimulationPanel extends JPanel {
simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer()); simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
simulationTableModel.setColumnWidths(simulationTable.getColumnModel()); simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
// Mouse listener to act on double-clicks // Mouse listener to act on double-clicks
simulationTable.addMouseListener(new MouseAdapter() { simulationTable.addMouseListener(new MouseAdapter() {
@Override @Override
@ -499,15 +522,15 @@ public class SimulationPanel extends JPanel {
return; return;
} }
int selected = simulationTable.convertRowIndexToModel(selectedRow); int selected = simulationTable.convertRowIndexToModel(selectedRow);
int column = simulationTable.columnAtPoint(e.getPoint()); int column = simulationTable.columnAtPoint(e.getPoint());
if (column == 0) { if (column == 0) {
SimulationWarningDialog.showWarningDialog(SimulationPanel.this, document.getSimulations().get(selected)); SimulationWarningDialog.showWarningDialog(SimulationPanel.this, document.getSimulations().get(selected));
} else { } else {
simulationTable.clearSelection(); simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(selectedRow, selectedRow); simulationTable.addRowSelectionInterval(selectedRow, selectedRow);
openDialog(document.getSimulations().get(selected)); openDialog(document.getSimulations().get(selected));
} }
} else { } else {
@ -515,7 +538,7 @@ public class SimulationPanel extends JPanel {
} }
} }
}); });
document.addDocumentChangeListener(new DocumentChangeListener() { document.addDocumentChangeListener(new DocumentChangeListener() {
@Override @Override
public void documentChanged(DocumentChangeEvent event) { public void documentChanged(DocumentChangeEvent event) {
@ -524,10 +547,10 @@ public class SimulationPanel extends JPanel {
simulationTableModel.fireTableDataChanged(); simulationTableModel.fireTableDataChanged();
} }
}); });
// Fire table change event when the rocket changes // Fire table change event when the rocket changes
document.getRocket().addComponentChangeListener(new ComponentChangeListener() { document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
@Override @Override
@ -535,14 +558,14 @@ public class SimulationPanel extends JPanel {
fireMaintainSelection(); fireMaintainSelection();
} }
}); });
JScrollPane scrollpane = new JScrollPane(simulationTable); JScrollPane scrollpane = new JScrollPane(simulationTable);
this.add(scrollpane, "spanx, grow, wrap rel"); this.add(scrollpane, "spanx, grow, wrap rel");
updateButtonStates(); updateButtonStates();
} }
private void updateButtonStates() { private void updateButtonStates() {
int[] selection = simulationTable.getSelectedRows(); int[] selection = simulationTable.getSelectedRows();
if (selection.length == 0) { if (selection.length == 0) {
@ -560,13 +583,13 @@ public class SimulationPanel extends JPanel {
runButton.setEnabled(true); runButton.setEnabled(true);
deleteButton.setEnabled(true); deleteButton.setEnabled(true);
} }
} }
public ListSelectionModel getSimulationListSelectionModel() { public ListSelectionModel getSimulationListSelectionModel() {
return simulationTable.getSelectionModel(); return simulationTable.getSelectionModel();
} }
private void openDialog(boolean plotMode, final Simulation... sims) { private void openDialog(boolean plotMode, final Simulation... sims) {
SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sims); SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sims);
if (plotMode) { if (plotMode) {
@ -575,7 +598,7 @@ public class SimulationPanel extends JPanel {
d.setVisible(true); d.setVisible(true);
fireMaintainSelection(); fireMaintainSelection();
} }
private void openDialog(final Simulation sim) { private void openDialog(final Simulation sim) {
boolean plotMode = false; boolean plotMode = false;
if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.EXTERNAL)) { if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.EXTERNAL)) {
@ -583,7 +606,7 @@ public class SimulationPanel extends JPanel {
} }
openDialog(plotMode, sim); openDialog(plotMode, sim);
} }
private void fireMaintainSelection() { private void fireMaintainSelection() {
int[] selection = simulationTable.getSelectedRows(); int[] selection = simulationTable.getSelectedRows();
simulationTableModel.fireTableDataChanged(); simulationTableModel.fireTableDataChanged();
@ -593,24 +616,24 @@ public class SimulationPanel extends JPanel {
simulationTable.addRowSelectionInterval(row, row); simulationTable.addRowSelectionInterval(row, row);
} }
} }
private enum SimulationTableColumns { private enum SimulationTableColumns {
} }
private class JLabelRenderer extends DefaultTableCellRenderer { private class JLabelRenderer extends DefaultTableCellRenderer {
@Override @Override
public Component getTableCellRendererComponent(JTable table, public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row, Object value, boolean isSelected, boolean hasFocus, int row,
int column) { int column) {
if (row < 0 || row >= document.getSimulationCount()) if (row < 0 || row >= document.getSimulationCount())
return super.getTableCellRendererComponent(table, value, return super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column); isSelected, hasFocus, row, column);
row = table.getRowSorter().convertRowIndexToModel(row); row = table.getRowSorter().convertRowIndexToModel(row);
// A JLabel is self-contained and has set its own tool tip // A JLabel is self-contained and has set its own tool tip
if (value instanceof JLabel) { if (value instanceof JLabel) {
JLabel label = (JLabel) value; JLabel label = (JLabel) value;
@ -619,66 +642,66 @@ public class SimulationPanel extends JPanel {
else else
label.setBackground(table.getBackground()); label.setBackground(table.getBackground());
label.setOpaque(true); label.setOpaque(true);
label.setToolTipText(getSimulationToolTip(document.getSimulation(row))); label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
return label; return label;
} }
Component component = super.getTableCellRendererComponent(table, value, Component component = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column); isSelected, hasFocus, row, column);
if (component instanceof JComponent) { if (component instanceof JComponent) {
((JComponent) component).setToolTipText(getSimulationToolTip( ((JComponent) component).setToolTipText(getSimulationToolTip(
document.getSimulation(row))); document.getSimulation(row)));
} }
return component; return component;
} }
private String getSimulationToolTip(Simulation sim) { private String getSimulationToolTip(Simulation sim) {
String tip; String tip;
FlightData data = sim.getSimulatedData(); FlightData data = sim.getSimulatedData();
tip = "<html><b>" + sim.getName() + "</b><br>"; tip = "<html><b>" + sim.getName() + "</b><br>";
switch (sim.getStatus()) { switch (sim.getStatus()) {
case UPTODATE: case UPTODATE:
tip += trans.get("simpanel.ttip.uptodate") + "<br>"; tip += trans.get("simpanel.ttip.uptodate") + "<br>";
break; break;
case LOADED: case LOADED:
tip += trans.get("simpanel.ttip.loaded") + "<br>"; tip += trans.get("simpanel.ttip.loaded") + "<br>";
break; break;
case OUTDATED: case OUTDATED:
tip += trans.get("simpanel.ttip.outdated") + "<br>"; tip += trans.get("simpanel.ttip.outdated") + "<br>";
break; break;
case EXTERNAL: case EXTERNAL:
tip += trans.get("simpanel.ttip.external") + "<br>"; tip += trans.get("simpanel.ttip.external") + "<br>";
return tip; return tip;
case NOT_SIMULATED: case NOT_SIMULATED:
tip += trans.get("simpanel.ttip.notSimulated"); tip += trans.get("simpanel.ttip.notSimulated");
return tip; return tip;
} }
if (data == null) { if (data == null) {
tip += trans.get("simpanel.ttip.noData"); tip += trans.get("simpanel.ttip.noData");
return tip; return tip;
} }
WarningSet warnings = data.getWarningSet(); WarningSet warnings = data.getWarningSet();
if (warnings.isEmpty()) { if (warnings.isEmpty()) {
tip += trans.get("simpanel.ttip.noWarnings"); tip += trans.get("simpanel.ttip.noWarnings");
return tip; return tip;
} }
tip += trans.get("simpanel.ttip.warnings"); tip += trans.get("simpanel.ttip.warnings");
for (Warning w : warnings) { for (Warning w : warnings) {
tip += "<br>" + w.toString(); tip += "<br>" + w.toString();
} }
return tip; return tip;
} }
} }
} }

View File

@ -125,6 +125,7 @@ public class DesignReport {
private static final String SIZE = "Size"; private static final String SIZE = "Size";
private static final String ALTITUDE = "Altitude"; private static final String ALTITUDE = "Altitude";
private static final String FLIGHT_TIME = "Flight Time"; private static final String FLIGHT_TIME = "Flight Time";
private static final String OPTIMUM_DELAY = "Optimum Delay";
private static final String TIME_TO_APOGEE = "Time to Apogee"; private static final String TIME_TO_APOGEE = "Time to Apogee";
private static final String VELOCITY_OFF_PAD = "Velocity off Pad"; private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
private static final String MAX_VELOCITY = "Max Velocity"; private static final String MAX_VELOCITY = "Max Velocity";
@ -468,6 +469,9 @@ public class DesignReport {
labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2)); labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2)); labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
labelTable.addCell(ITextHelper.createCell(OPTIMUM_DELAY, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getBranch(0).getOptimumDelay()), 2, 2));
labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2)); labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));