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.Velocityoffrod = Velocity off rod
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.Maxvelocity = Max. velocity
simpanel.col.Maxacceleration = Max. acceleration

View File

@ -536,6 +536,7 @@ public class OpenRocketSaver extends RocketSaver {
StringBuilder sb = new StringBuilder();
sb.append("<databranch name=\"");
sb.append(TextUtil.escapeXML(branch.getBranchName()));
sb.append("\" ");
// 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++) {
if (i > 0)
sb.append(",");
@ -590,8 +603,6 @@ public class OpenRocketSaver extends RocketSaver {
writeln("</databranch>");
}
/* TODO: LOW: This is largely duplicated from above! */
private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
int count = 0;

View File

@ -42,6 +42,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
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
// 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.

View File

@ -48,6 +48,23 @@ class FlightDataHandler extends AbstractElementHandler {
dataHandler = new FlightDataBranchHandler(attributes.get("name"),
attributes.get("types"),
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;
}

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.SimulationLaunchException;
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.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
@ -120,8 +121,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = status.getRocketVelocity();
try {
double maxAlt = Double.NEGATIVE_INFINITY;
// Start the simulation
while (handleEvents()) {
@ -149,8 +148,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, status.getRocketPosition().z)));
if (status.getRocketPosition().z > maxAlt) {
maxAlt = status.getRocketPosition().z;
if (status.getRocketPosition().z > status.getMaxAlt()) {
status.setMaxAlt(status.getRocketPosition().z);
}
@ -189,7 +188,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// 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(),
status.getConfiguration().getRocket()));
}
@ -464,6 +464,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Mark apogee as reached
status.setApogeeReached(true);
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;
case RECOVERY_DEVICE_DEPLOYMENT:
@ -501,6 +506,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.setLiftoff(true);
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.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> 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 Mutable mutable = new Mutable();
@ -73,12 +81,12 @@ public class FlightDataBranch implements Monitorable {
*/
public FlightDataBranch() {
branchName = "Empty branch";
for (FlightDataType type : FlightDataType.ALL_TYPES){
for (FlightDataType type : FlightDataType.ALL_TYPES) {
this.setValue(type, Double.NaN);
}
this.immute();
}
/**
* 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);
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);
}
@ -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.
*
@ -241,6 +293,34 @@ public class FlightDataBranch implements Monitorable {
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

View File

@ -29,7 +29,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
private String motorID = null;
private Simulation simulation; // The parent simulation
private double launchRodLength = 1;
/** 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 GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
private WindModel windModel;
private AtmosphericModel atmosphericModel;
private GravityModel gravityModel;
@ -53,25 +53,25 @@ public class SimulationConditions implements Monitorable, Cloneable {
private AerodynamicCalculator aerodynamicCalculator;
private MassCalculator massCalculator;
private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
private double maximumAngleStep = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
/* Whether to calculate additional data or only primary simulation figures */
private boolean calculateExtras = true;
private List<SimulationListener> simulationListeners = new ArrayList<SimulationListener>();
private int randomSeed = 0;
private int modID = 0;
private int modIDadd = 0;
public AerodynamicCalculator getAerodynamicCalculator() {
return aerodynamicCalculator;
}
@ -253,7 +253,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
}
public int getRandomSeed() {
return randomSeed;
}
@ -267,8 +267,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
public void setSimulation(Simulation sim) {
this.simulation = sim;
}
public Simulation getSimulation(){
public Simulation getSimulation() {
return this.simulation;
}
@ -291,7 +291,12 @@ public class SimulationConditions implements Monitorable, Cloneable {
public SimulationConditions clone() {
try {
// 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) {
throw new BugException(e);
}

View File

@ -27,10 +27,6 @@ import net.sf.openrocket.util.WorldCoordinate;
*/
public class SimulationStatus implements Monitorable {
/*
* NOTE! All fields must be added to copyFrom() method!!
*/
private SimulationConditions simulationConditions;
private Configuration configuration;
private MotorInstanceConfiguration motorConfiguration;
@ -51,12 +47,12 @@ public class SimulationStatus implements Monitorable {
// Set of burnt out motors
Set<MotorId> motorBurntOut = new HashSet<MotorId>();
/** Nanosecond time when the simulation was started. */
private long simulationStartWallTime = Long.MIN_VALUE;
/** Set to true when a motor has ignited. */
private boolean motorIgnited = false;
@ -83,38 +79,40 @@ public class SimulationStatus implements Monitorable {
/** Available for special purposes by the listeners. */
private final Map<String, Object> extraData = new HashMap<String, Object>();
double maxAlt = Double.NEGATIVE_INFINITY;
double maxAltTime = 0;
private int modID = 0;
private int modIDadd = 0;
public SimulationStatus( Configuration configuration,
public SimulationStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions ) {
SimulationConditions simulationConditions) {
this.simulationConditions = simulationConditions;
this.configuration = configuration;
this.motorConfiguration = motorConfiguration;
this.time = 0;
this.previousTimeStep = this.simulationConditions.getTimeStep();
this.position = Coordinate.NUL;
this.velocity = Coordinate.NUL;
this.worldPosition = this.simulationConditions.getLaunchSite();
// Initialize to roll angle with least stability w.r.t. the wind
Quaternion o;
FlightConditions cond = new FlightConditions(this.configuration);
this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null);
double angle = -cond.getTheta() - this.simulationConditions.getLaunchRodDirection();
o = Quaternion.rotation(new Coordinate(0, 0, angle));
// 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, 0, this.simulationConditions.getLaunchRodDirection())));
this.orientation = o;
this.rotationVelocity = Coordinate.NUL;
/*
* Calculate the effective launch rod length taking into account launch lugs.
* 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.simulationStartWallTime = System.nanoTime();
this.motorIgnited = false;
this.liftoff = false;
this.launchRodCleared = false;
this.apogeeReached = false;
this.warnings = new WarningSet();
}
/**
@ -163,7 +161,7 @@ public class SimulationStatus implements Monitorable {
*
* @param orig the object from which to copy
*/
public SimulationStatus( SimulationStatus orig ) {
public SimulationStatus(SimulationStatus orig) {
this.simulationConditions = orig.simulationConditions.clone();
this.configuration = orig.configuration.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);
}
public Quaternion getRocketOrientationQuaternion() {
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.modID++;
}
@ -393,6 +391,24 @@ public class SimulationStatus implements Monitorable {
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() {
return deployedRecoveryDevices;
}
@ -477,5 +493,5 @@ public class SimulationStatus implements Monitorable {
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.
*/
@Override
public FlightDataType[] getFlightDataTypes(){
public FlightDataType[] getFlightDataTypes() {
return new FlightDataType[] {};
}
@ -183,4 +183,9 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
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.exception.SimulationException;
public interface SimulationListener {
/**
* Listen to simulation events and possibly take action.
*
* 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
@ -83,5 +87,5 @@ public interface SimulationListener {
*/
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 {
private final String name;
private final String toolTip;
/**
* Create a new column with specified name. Additionally, the {@link #getValueAt(int)}
@ -15,6 +16,17 @@ public abstract class Column {
*/
public Column(String 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() {
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;
}
public ValueColumn( String name, String toolTip, UnitGroup unit ) {
super( name, toolTip );
this.unit = unit;
}
@Override
public Object getValueAt(int 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.WarningSet;
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.DoubleModel;
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.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);

View File

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

View File

@ -125,6 +125,7 @@ public class DesignReport {
private static final String SIZE = "Size";
private static final String ALTITUDE = "Altitude";
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 VELOCITY_OFF_PAD = "Velocity off Pad";
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(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(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));