diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index be5724c5d..87cb4f676 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -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 diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index a3f085331..a21354f7f 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -536,6 +536,7 @@ public class OpenRocketSaver extends RocketSaver { StringBuilder sb = new StringBuilder(); sb.append(" 0) sb.append(","); @@ -590,8 +603,6 @@ public class OpenRocketSaver extends RocketSaver { writeln(""); } - - /* TODO: LOW: This is largely duplicated from above! */ private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) { int count = 0; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java index 546ed02f6..e8ea6b80f 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java @@ -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. diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java index bb2a78976..5883ba17e 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java @@ -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; } diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index fb8d32020..ab3e35435 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -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(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; + } + } } diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java index 76bef7642..0f523b2f1 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -36,7 +36,15 @@ public class FlightDataBranch implements Monitorable { private final Map maxValues = new HashMap(); private final Map minValues = new HashMap(); - + /** + * 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 events = new ArrayList(); 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 diff --git a/core/src/net/sf/openrocket/simulation/SimulationConditions.java b/core/src/net/sf/openrocket/simulation/SimulationConditions.java index ffd9e003a..61bad44c6 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationConditions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationConditions.java @@ -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 simulationListeners = new ArrayList(); - + 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(this.simulationListeners.size()); + for (SimulationListener listener : this.simulationListeners) { + clone.simulationListeners.add(listener.clone()); + } + return clone; } catch (CloneNotSupportedException e) { throw new BugException(e); } diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index 30fce5772..79b266a5a 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -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 motorBurntOut = new HashSet(); - - + + /** 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 extraData = new HashMap(); - + 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 getDeployedRecoveryDevices() { return deployedRecoveryDevices; } @@ -477,5 +493,5 @@ public class SimulationStatus implements Monitorable { eventQueue.getModID() + warnings.getModID()); } - + } diff --git a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java index c3211f90c..0c8a3d32d 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java @@ -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(); + } + } diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java index ba727f2bb..bdbf90ffc 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java @@ -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; } diff --git a/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java b/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java new file mode 100644 index 000000000..e6dcdc7aa --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java @@ -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; + } + +} diff --git a/swing/resources/datafiles/examples/A simple model rocket.ork b/swing/resources/datafiles/examples/A simple model rocket.ork index febaadaeb..2932f25e3 100644 Binary files a/swing/resources/datafiles/examples/A simple model rocket.ork and b/swing/resources/datafiles/examples/A simple model rocket.ork differ diff --git a/swing/resources/datafiles/examples/Apocalypse with decals.ork b/swing/resources/datafiles/examples/Apocalypse with decals.ork index 0b368faad..57c63fdc8 100644 Binary files a/swing/resources/datafiles/examples/Apocalypse with decals.ork and b/swing/resources/datafiles/examples/Apocalypse with decals.ork differ diff --git a/swing/resources/datafiles/examples/Boosted Dart.ork b/swing/resources/datafiles/examples/Boosted Dart.ork index 6638ec6b9..8bd8df078 100644 Binary files a/swing/resources/datafiles/examples/Boosted Dart.ork and b/swing/resources/datafiles/examples/Boosted Dart.ork differ diff --git a/swing/resources/datafiles/examples/Clustered rocket design.ork b/swing/resources/datafiles/examples/Clustered rocket design.ork index 825059deb..b16f089c5 100644 Binary files a/swing/resources/datafiles/examples/Clustered rocket design.ork and b/swing/resources/datafiles/examples/Clustered rocket design.ork differ diff --git a/swing/resources/datafiles/examples/High Power Airstart.ork b/swing/resources/datafiles/examples/High Power Airstart.ork index 87ce4e0d1..edfb6e125 100644 Binary files a/swing/resources/datafiles/examples/High Power Airstart.ork and b/swing/resources/datafiles/examples/High Power Airstart.ork differ diff --git a/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork b/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork index 19fece7dd..d3c120da4 100644 Binary files a/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork and b/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork differ diff --git a/swing/resources/datafiles/examples/Preset Usage.ork b/swing/resources/datafiles/examples/Preset Usage.ork index 932e24dcd..358f2f079 100644 Binary files a/swing/resources/datafiles/examples/Preset Usage.ork and b/swing/resources/datafiles/examples/Preset Usage.ork differ diff --git a/swing/resources/datafiles/examples/Roll-stabilized rocket.ork b/swing/resources/datafiles/examples/Roll-stabilized rocket.ork index 35cd43d95..4d7d84a03 100644 Binary files a/swing/resources/datafiles/examples/Roll-stabilized rocket.ork and b/swing/resources/datafiles/examples/Roll-stabilized rocket.ork differ diff --git a/swing/resources/datafiles/examples/Simulation listeners.ork b/swing/resources/datafiles/examples/Simulation listeners.ork index 8c1070fd5..d4fbae528 100644 Binary files a/swing/resources/datafiles/examples/Simulation listeners.ork and b/swing/resources/datafiles/examples/Simulation listeners.ork differ diff --git a/swing/resources/datafiles/examples/TARC Payloader.ork b/swing/resources/datafiles/examples/TARC Payloader.ork index 8508ea1a3..2d9ecbf0f 100644 Binary files a/swing/resources/datafiles/examples/TARC Payloader.ork and b/swing/resources/datafiles/examples/TARC Payloader.ork differ diff --git a/swing/resources/datafiles/examples/Three-stage rocket.ork b/swing/resources/datafiles/examples/Three-stage rocket.ork index 1775c6774..39b3a02f5 100644 Binary files a/swing/resources/datafiles/examples/Three-stage rocket.ork and b/swing/resources/datafiles/examples/Three-stage rocket.ork differ diff --git a/swing/src/net/sf/openrocket/gui/adaptors/Column.java b/swing/src/net/sf/openrocket/gui/adaptors/Column.java index 36efe2ace..1c51f98af 100644 --- a/swing/src/net/sf/openrocket/gui/adaptors/Column.java +++ b/swing/src/net/sf/openrocket/gui/adaptors/Column.java @@ -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; + } } diff --git a/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java b/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java new file mode 100644 index 000000000..a1c54c2f7 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java @@ -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; + } + }; + } + +} diff --git a/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java b/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java index e2922f0af..052f9b975 100644 --- a/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java +++ b/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java @@ -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); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index 9a2320bf7..5f2d90b3a 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -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); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java index 706014fcf..b39d683bb 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java @@ -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); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java index 41aecb430..e3b1d1278 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java @@ -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()); diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 4a238463a..c25d19eb1 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -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"), - //// This operation cannot be undone. - trans.get("simpanel.dlg.lbl.DeleteSim2"), - "", - panel }, + //// Delete the selected simulations? + trans.get("simpanel.dlg.lbl.DeleteSim1"), + //// This operation cannot be undone. + 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 = "" + sim.getName() + "
"; switch (sim.getStatus()) { case UPTODATE: tip += trans.get("simpanel.ttip.uptodate") + "
"; break; - + case LOADED: tip += trans.get("simpanel.ttip.loaded") + "
"; break; - + case OUTDATED: tip += trans.get("simpanel.ttip.outdated") + "
"; break; - + case EXTERNAL: tip += trans.get("simpanel.ttip.external") + "
"; 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 += "
" + w.toString(); } - + return tip; } - + } } diff --git a/swing/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java index 776a79379..550381a1c 100644 --- a/swing/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/swing/src/net/sf/openrocket/gui/print/DesignReport.java @@ -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));