@@ -40,7 +40,6 @@ public abstract class AbstractMotorLoader implements MotorLoader {
protected abstract List load(Reader reader, String filename) throws IOException;
-
/**
* Return the default charset to use when loading rocket files of this type.
*
@@ -52,11 +51,11 @@ public abstract class AbstractMotorLoader implements MotorLoader {
protected abstract Charset getDefaultCharset();
-
-
+
+
////////// Helper methods //////////
-
+
/**
* Calculate the mass of a motor at distinct points in time based on the
* initial total mass, propellant weight and thrust.
@@ -88,6 +87,7 @@ public abstract class AbstractMotorLoader implements MotorLoader {
double f1 = thrust.get(i);
double dm = 0.5 * (f0 + f1) * (t1 - t0);
+
deltam.add(dm);
totalMassChange += dm;
t0 = t1;
@@ -99,13 +99,16 @@ public abstract class AbstractMotorLoader implements MotorLoader {
scale = prop / totalMassChange;
for (double dm : deltam) {
total -= dm * scale;
+ // to correct negative mass error condition: (caused by rounding errors in the above loop)
+ if (total < 0) {
+ total = 0;
+ }
mass.add(total);
+
}
-
return mass;
}
-
/**
* Helper method to remove a delay (or plugged) from the end of a motor designation,
* if present.
@@ -121,7 +124,6 @@ public abstract class AbstractMotorLoader implements MotorLoader {
}
-
/**
* Helper method to tokenize a string using whitespace as the delimiter.
*/
@@ -168,7 +170,7 @@ public abstract class AbstractMotorLoader implements MotorLoader {
}
-
+
@SuppressWarnings("unchecked")
protected static void finalizeThrustCurve(List time, List thrust,
List... lists) {
diff --git a/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java b/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java
index ec1bd3156..838e2d8fa 100644
--- a/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java
+++ b/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java
@@ -135,8 +135,8 @@ public class RockSimMotorLoader extends AbstractMotorLoader {
private final double initMass;
private final double propMass;
private final Motor.Type type;
- private boolean calculateMass;
- private boolean calculateCG;
+ private boolean calculateMass = false;
+ private boolean calculateCG = false;
private String description = "";
@@ -324,8 +324,8 @@ public class RockSimMotorLoader extends AbstractMotorLoader {
if (time == null || time.size() == 0)
throw new SAXException("Illegal motor data");
-
finalizeThrustCurve(time, force, mass, cg);
+
final int n = time.size();
if (hasIllegalValue(mass))
@@ -336,6 +336,7 @@ public class RockSimMotorLoader extends AbstractMotorLoader {
if (calculateMass) {
mass = calculateMass(time, force, initMass, propMass);
}
+
if (calculateCG) {
for (int i = 0; i < n; i++) {
cg.set(i, length / 2);
diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
index 108c634fa..295c544e7 100644
--- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
+++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
@@ -580,6 +580,7 @@ public class OpenRocketSaver extends RocketSaver {
StringBuilder sb = new StringBuilder();
sb.append(" 0)
sb.append(",");
@@ -634,8 +647,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/DocumentConfig.java b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java
index dc14486d0..2e909207d 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java
@@ -314,6 +314,14 @@ class DocumentConfig {
// MassComponent
setters.put("MassComponent:mass", new DoubleSetter(
Reflection.findMethod(MassComponent.class, "setComponentMass", double.class)));
+ /*setters.put("MassComponent:masscomponenttype", new DoubleSetter(
+ Reflection.findMethod(MassComponent.class, "setMassComponentType", double.class)));*/
+ setters.put("MassComponent:masscomponenttype", new EnumSetter(
+ Reflection.findMethod(MassComponent.class, "setMassComponentType", MassComponent.MassComponentType.class),
+ MassComponent.MassComponentType.class));
+ /* setters.put("Transition:shape", new EnumSetter(
+ Reflection.findMethod(Transition.class, "setType", Transition.Shape.class),
+ Transition.Shape.class));*/
// ShockCord
setters.put("ShockCord:cordlength", new DoubleSetter(
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/file/openrocket/savers/MassComponentSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java
index 093303c26..8cfddff50 100644
--- a/core/src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java
+++ b/core/src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java
@@ -2,31 +2,36 @@ package net.sf.openrocket.file.openrocket.savers;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.MassComponent.MassComponentType;
public class MassComponentSaver extends MassObjectSaver {
-
+
private static final MassComponentSaver instance = new MassComponentSaver();
-
+
public static List getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
List list = new ArrayList();
-
+
list.add("");
instance.addParams(c, list);
list.add("");
-
+
return list;
}
-
+
@Override
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) {
super.addParams(c, elements);
-
+
MassComponent mass = (MassComponent) c;
-
+
elements.add("" + mass.getMass() + "");
+
+ MassComponentType type = mass.getMassComponentType();
+ elements.add("" + type.name().toLowerCase(Locale.ENGLISH) + "");
+
}
-
}
diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java
index 7df686e5a..3efe9f228 100644
--- a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java
+++ b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java
@@ -54,7 +54,7 @@ public class RocketComponentSaver {
String name = decal.getImage().getName();
double rotation = decal.getRotation();
EdgeMode edgeMode = decal.getEdgeMode();
- elements.add("");
+ elements.add("");
Coordinate center = decal.getCenter();
elements.add("
");
Coordinate offset = decal.getOffset();
diff --git a/core/src/net/sf/openrocket/file/rocksim/RocksimCommonConstants.java b/core/src/net/sf/openrocket/file/rocksim/RocksimCommonConstants.java
index bde3d072a..86cc5861d 100644
--- a/core/src/net/sf/openrocket/file/rocksim/RocksimCommonConstants.java
+++ b/core/src/net/sf/openrocket/file/rocksim/RocksimCommonConstants.java
@@ -9,6 +9,7 @@ public class RocksimCommonConstants {
public static final String WALL_THICKNESS = "WallThickness";
public static final String SHAPE_PARAMETER = "ShapeParameter";
public static final String ATTACHED_PARTS = "AttachedParts";
+ public static final String SUBASSEMBLY = "SubAssembly";
public static final String BODY_TUBE = "BodyTube";
public static final String FIN_SET = "FinSet";
public static final String CUSTOM_FIN_SET = "CustomFinSet";
diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java
index 7a909a4c5..3738a160f 100644
--- a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java
+++ b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java
@@ -3,8 +3,6 @@
*/
package net.sf.openrocket.file.rocksim.importt;
-import java.util.HashMap;
-
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.DocumentLoadingContext;
import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
@@ -12,20 +10,22 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import java.util.HashMap;
+
/**
- * A SAX handler for the Rocksim AttachedParts XML type.
+ * A SAX handler for the Rocksim AttachedParts XML type.
*/
class AttachedPartsHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
-
+
/** The parent component. */
private final RocketComponent component;
-
+
/**
* Constructor.
- *
+ *
* @param c the parent
- *
+ *
* @throws IllegalArgumentException thrown if c is null
*/
public AttachedPartsHandler(DocumentLoadingContext context, RocketComponent c) throws IllegalArgumentException {
@@ -33,10 +33,18 @@ class AttachedPartsHandler extends AbstractElementHandler {
throw new IllegalArgumentException("The parent component of any attached part may not be null.");
}
this.context = context;
- component = c;
+ this.component = c;
}
-
- @Override
+
+ DocumentLoadingContext getContext() {
+ return context;
+ }
+
+ RocketComponent getComponent() {
+ return component;
+ }
+
+ @Override
public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) {
if (RocksimCommonConstants.FIN_SET.equals(element)) {
return new FinSetHandler(context, component);
@@ -65,6 +73,9 @@ class AttachedPartsHandler extends AbstractElementHandler {
if (RocksimCommonConstants.TRANSITION.equals(element)) {
return new TransitionHandler(context, component, warnings);
}
+ if (RocksimCommonConstants.SUBASSEMBLY.equals(element)) {
+ return new SubAssemblyHandler(context, component);
+ }
if (RocksimCommonConstants.TUBE_FIN_SET.equals(element)) {
warnings.add("Tube fins are not currently supported. Ignoring.");
}
diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java
new file mode 100644
index 000000000..3dbc11628
--- /dev/null
+++ b/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java
@@ -0,0 +1,43 @@
+
+package net.sf.openrocket.file.rocksim.importt;
+
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.file.DocumentLoadingContext;
+import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
+import net.sf.openrocket.file.simplesax.ElementHandler;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+
+import java.util.HashMap;
+
+/**
+ * This class handles Rocksim 'SubAssembly' elements. They are similar to 'AttachedParts' (which is why this class is subclassed from
+ * AttachedPartsHandler) with some key differences. In Rocksim, AttachedParts elements can contain SubAssembly elements, which can in turn
+ * contain AttachedParts elements. To represent them in OR, SubAssembly elements are treated as children of the stage - much like a nose cone or
+ * external body tube.
+ */
+public class SubAssemblyHandler extends AttachedPartsHandler {
+
+ public SubAssemblyHandler(final DocumentLoadingContext context, final RocketComponent c)
+ throws IllegalArgumentException {
+ //A bit of a risk here, but assign the subassembly to the stage, not to the component. This is because typically the
+ //first component within the subassembly will be an external component.
+ super(context, c.getStage());
+ }
+
+ @Override
+ public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) {
+ // We're already part of a subassembly, and then there are attached parts! Can't use an attached parts handler in this situation because
+ // the AttachedPartsHandler assumes that all body tubes are inner body tubes (Rocksim makes no distinction). OR does not allow things
+ // like fins to be attached to inner body tubes - which is often what these Rocksim subassemblies contain. So just return this instance
+ // which treats body tubes as external body tubes.
+ if (RocksimCommonConstants.ATTACHED_PARTS.equals(element)) {
+ return this;
+ }
+ // The key override of this class - treat body tubes as external body tubes.
+ else if (RocksimCommonConstants.BODY_TUBE.equals(element)) {
+ return new BodyTubeHandler(getContext(), getComponent(), warnings);
+ }
+ return super.openElement(element, attributes, warnings);
+ }
+
+}
diff --git a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java
index 3a0e71a2a..888882af9 100644
--- a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java
+++ b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java
@@ -134,11 +134,14 @@ public class ThrustCurveMotor implements Motor, Comparable, Se
if (c.isNaN()) {
throw new IllegalArgumentException("Invalid CG " + c);
}
- if (c.x < 0 || c.x > length) {
- throw new IllegalArgumentException("Invalid CG position " + c.x);
+ if (c.x < 0) {
+ throw new IllegalArgumentException("Invalid CG position " + String.format("%f", c.x) + ": CG is below the start of the motor.");
+ }
+ if (c.x > length) {
+ throw new IllegalArgumentException("Invalid CG position: " + String.format("%f", c.x) + ": CG is above the end of the motor.");
}
if (c.weight < 0) {
- throw new IllegalArgumentException("Negative mass " + c.weight);
+ throw new IllegalArgumentException("Negative mass " + c.weight + "at time=" + time[Arrays.asList(cg).indexOf(c)]);
}
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/Configuration.java b/core/src/net/sf/openrocket/rocketcomponent/Configuration.java
index 4cad9533a..e426df1d7 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/Configuration.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/Configuration.java
@@ -189,11 +189,10 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi
*/
public void release() {
rocket.removeComponentChangeListener(this);
- listenerList = null;
+ listenerList = new ArrayList();
rocket = null;
}
-
//////////////// Listeners ////////////////
@Override
diff --git a/core/src/net/sf/openrocket/rocketcomponent/MassComponent.java b/core/src/net/sf/openrocket/rocketcomponent/MassComponent.java
index 81e5a63d8..0bfb75c03 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/MassComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/MassComponent.java
@@ -15,6 +15,29 @@ public class MassComponent extends MassObject {
private double mass = 0;
+ public static enum MassComponentType {
+ MASSCOMPONENT(Application.getTranslator().get("MassComponent.MassComponent")),
+ ALTIMETER(Application.getTranslator().get("MassComponent.Altimeter")),
+ FLIGHTCOMPUTER(Application.getTranslator().get("MassComponent.FlightComputer")),
+ DEPLOYMENTCHARGE(Application.getTranslator().get("MassComponent.DeploymentCharge")),
+ TRACKER(Application.getTranslator().get("MassComponent.Tracker")),
+ PAYLOAD(Application.getTranslator().get("MassComponent.Payload")),
+ RECOVERYHARDWARE(Application.getTranslator().get("MassComponent.RecoveryHardware")),
+ BATTERY(Application.getTranslator().get("MassComponent.Battery"));
+
+ private String title;
+
+ MassComponentType(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+ }
+
+ private MassComponentType massComponentType = MassComponentType.MASSCOMPONENT;
public MassComponent() {
super();
@@ -67,6 +90,21 @@ public class MassComponent extends MassObject {
return trans.get("MassComponent.MassComponent");
}
+ public final MassComponent.MassComponentType getMassComponentType() {
+ mutex.verify();
+ return this.massComponentType;
+ }
+
+ public void setMassComponentType(MassComponent.MassComponentType compType) {
+ mutex.verify();
+ if (this.massComponentType == compType) {
+ return;
+ }
+ checkState();
+ this.massComponentType = compType;
+ fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+ }
+
@Override
public boolean allowsChildren() {
return false;
diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
index baa175065..30cb2ceaa 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
@@ -1121,6 +1121,19 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
return getComponentMass();
}
+ /**
+ * Return the mass of this component and all of its subcomponents.
+ */
+ public final double getSectionMass() {
+ Double massSubtotal = getMass();
+ mutex.verify();
+ for (RocketComponent rc : children) {
+ massSubtotal += rc.getSectionMass();
+ }
+
+ return massSubtotal;
+ }
+
/**
* Return the (possibly overridden) center of gravity and mass.
*
diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
index fb8d32020..b7d8889a0 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;
@@ -42,8 +43,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private SimulationStepper landingStepper = new BasicLandingStepper();
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
- // Constant holding 20 degress in radians. This is the AOA condition
- // necessary to transistion to tumbling.
+ // Constant holding 20 degrees in radians. This is the AOA condition
+ // necessary to transition to tumbling.
private final static double AOA_TUMBLE_CONDITION = Math.PI / 9.0;
// The thrust must be below this value for the transition to tumbling.
@@ -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()));
}
@@ -223,12 +223,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
if (wantToTumble) {
final boolean tooMuchThrust = t > THRUST_TUMBLE_CONDITION;
final boolean isSustainer = status.getConfiguration().isStageActive(0);
- final boolean notUntilApogee = isSustainer && !status.isApogeeReached();
+ final boolean isApogee = status.isApogeeReached();
if (tooMuchThrust) {
status.getWarnings().add(Warning.TUMBLE_UNDER_THRUST);
- } else if (notUntilApogee) {
- status.getWarnings().add(Warning.TUMBLE_BEFORE_APOGEE);
- } else {
+ } else if (isApogee) {
addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, status.getSimulationTime()));
status.setTumbling(true);
}
@@ -464,6 +462,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 +504,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 +612,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 f068db35f..584fea084 100644
--- a/core/src/net/sf/openrocket/simulation/SimulationConditions.java
+++ b/core/src/net/sf/openrocket/simulation/SimulationConditions.java
@@ -318,7 +318,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 acff4d4ff..24215b785 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;
@@ -83,6 +79,8 @@ 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;
@@ -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;
}
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/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java
index 07e9d36c5..0f9c7f3e7 100644
--- a/core/src/net/sf/openrocket/startup/Preferences.java
+++ b/core/src/net/sf/openrocket/startup/Preferences.java
@@ -30,6 +30,8 @@ public abstract class Preferences {
public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves";
public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
+ public static final String AUTO_RUN_SIMULATIONS = "AutoRunSimulations";
+
// Preferences related to data export
public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
@@ -55,6 +57,8 @@ public abstract class Preferences {
public static final String OPENGL_ENABLE_AA = "OpenGL_Antialiasing_Is_Enabled";
public static final String OPENGL_USE_FBO = "OpenGL_Use_FBO";
+ public static final String ROCKET_INFO_FONT_SIZE = "RocketInfoFontSize";
+
/*
* ******************************************************************************************
*
@@ -99,11 +103,23 @@ public abstract class Preferences {
this.putBoolean(CHECK_UPDATES, check);
}
+ public final boolean getAutoRunSimulations() {
+ return this.getBoolean(AUTO_RUN_SIMULATIONS, false);
+ }
+
+ public final void setAutoRunSimulations(boolean check) {
+ this.putBoolean(AUTO_RUN_SIMULATIONS, check);
+ }
+
public final double getDefaultMach() {
// TODO: HIGH: implement custom default mach number
return 0.3;
}
+ public final float getRocketInfoFontSize() {
+ return (float) (11.0 + 3 * Application.getPreferences().getChoice(Preferences.ROCKET_INFO_FONT_SIZE, 2, 0));
+ }
+
/**
* Enable/Disable the auto-opening of the last edited design file on startup.
*/
diff --git a/core/src/net/sf/openrocket/unit/UnitGroup.java b/core/src/net/sf/openrocket/unit/UnitGroup.java
index 19de190c3..1acf0a6ae 100644
--- a/core/src/net/sf/openrocket/unit/UnitGroup.java
+++ b/core/src/net/sf/openrocket/unit/UnitGroup.java
@@ -189,6 +189,7 @@ public class UnitGroup {
UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
UNITS_VELOCITY.addUnit(new GeneralUnit(0.3048, "ft/s"));
UNITS_VELOCITY.addUnit(new GeneralUnit(0.44704, "mph"));
+ UNITS_VELOCITY.addUnit(new GeneralUnit(0.51444445, "kt"));
UNITS_VELOCITY.setDefaultUnit(0);
UNITS_WINDSPEED = new UnitGroup();
@@ -196,6 +197,7 @@ public class UnitGroup {
UNITS_WINDSPEED.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
UNITS_WINDSPEED.addUnit(new GeneralUnit(0.3048, "ft/s"));
UNITS_WINDSPEED.addUnit(new GeneralUnit(0.44704, "mph"));
+ UNITS_WINDSPEED.addUnit(new GeneralUnit(0.51444445, "kt"));
UNITS_WINDSPEED.setDefaultUnit(0);
UNITS_ACCELERATION = new UnitGroup();
diff --git a/core/src/net/sf/openrocket/utils/L10nPropertyReport.java b/core/src/net/sf/openrocket/utils/L10nPropertyReport.java
index 93c459fcb..0241678a9 100644
--- a/core/src/net/sf/openrocket/utils/L10nPropertyReport.java
+++ b/core/src/net/sf/openrocket/utils/L10nPropertyReport.java
@@ -10,7 +10,7 @@ import java.util.Properties;
public class L10nPropertyReport {
- private static String[] supportedLocales = new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt" };
+ private static String[] supportedLocales = new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt", "tr", "zh_CN", "uk_UA" };
public static void main(String[] args) throws Exception {
diff --git a/core/test/net/sf/openrocket/file/motor/TestMotorLoader.java b/core/test/net/sf/openrocket/file/motor/TestMotorLoader.java
index dbc23e745..ee8e47978 100644
--- a/core/test/net/sf/openrocket/file/motor/TestMotorLoader.java
+++ b/core/test/net/sf/openrocket/file/motor/TestMotorLoader.java
@@ -1,6 +1,8 @@
package net.sf.openrocket.file.motor;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
@@ -16,6 +18,7 @@ public class TestMotorLoader {
private static final String DIGEST1 = "e523030bc96d5e63313b5723aaea267d";
private static final String DIGEST2 = "6a41f0f10b7283793eb0e6b389753729";
+ private static final String DIGEST3 = "e3164a735f9a50500f2725f0a33d246b";
@Test
@@ -25,7 +28,7 @@ public class TestMotorLoader {
test(loader, "test1.eng", DIGEST1);
test(loader, "test2.rse", DIGEST2);
test(loader, "test.zip", DIGEST2, DIGEST1);
-
+ test(loader, "test3.rse", DIGEST3);
}
@Test
@@ -38,6 +41,11 @@ public class TestMotorLoader {
test(new RockSimMotorLoader(), "test2.rse", DIGEST2);
}
+ @Test
+ public void testRocksimMotorLoader3() throws IOException {
+ test(new RockSimMotorLoader(), "test3.rse", DIGEST3);
+ }
+
@Test
public void testZipMotorLoader() throws IOException {
test(new ZipFileMotorLoader(), "test.zip", DIGEST2, DIGEST1);
diff --git a/core/test/net/sf/openrocket/file/motor/test3.rse b/core/test/net/sf/openrocket/file/motor/test3.rse
new file mode 100644
index 000000000..95a86c555
--- /dev/null
+++ b/core/test/net/sf/openrocket/file/motor/test3.rse
@@ -0,0 +1,27 @@
+
+
+
+ Water Rocket, 90 psi, 800g
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/RocksimLoaderTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/RocksimLoaderTest.java
index b7e6976e4..7711c463c 100644
--- a/core/test/net/sf/openrocket/file/rocksim/importt/RocksimLoaderTest.java
+++ b/core/test/net/sf/openrocket/file/rocksim/importt/RocksimLoaderTest.java
@@ -4,10 +4,6 @@
*/
package net.sf.openrocket.file.rocksim.importt;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
import net.sf.openrocket.file.DatabaseMotorFinder;
@@ -18,161 +14,225 @@ import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.Stage;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
-
import org.junit.Assert;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
/**
* RocksimLoader Tester.
*/
public class RocksimLoaderTest extends BaseTestCase {
-
- /**
- * Test a bug reported via automated bug report. I have been unable to reproduce this bug
- * (hanging finset off of an inner body tube) when creating a Rocksim file using Rocksim. The bug
- * is reproducible when manually modifying the Rocksim file, which is what is tested here.
- */
- @org.junit.Test
- public void testFinsOnInnerTube() throws Exception {
- RocksimLoader loader = new RocksimLoader();
- InputStream stream = this.getClass().getResourceAsStream("PodFins.rkt");
- Assert.assertNotNull("Could not open PodFins.rkt", stream);
- try {
- OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
- DocumentLoadingContext context = new DocumentLoadingContext();
- context.setOpenRocketDocument(doc);
- context.setMotorFinder(new DatabaseMotorFinder());
- loader.loadFromStream(context, new BufferedInputStream(stream));
- Rocket rocket = doc.getRocket();
- Assert.assertNotNull(rocket);
- } catch (IllegalStateException ise) {
- Assert.fail(ise.getMessage());
- }
- Assert.assertTrue(loader.getWarnings().size() == 2);
- }
-
- /**
- * Method: loadFromStream(InputStream source)
- *
- * @throws Exception thrown if something goes awry
- */
- @org.junit.Test
- public void testLoadFromStream() throws Exception {
- RocksimLoader loader = new RocksimLoader();
- //Stupid single stage rocket
- OpenRocketDocument doc = loadRocksimRocket(loader);
- InputStream stream;
-
- Assert.assertNotNull(doc);
- Rocket rocket = doc.getRocket();
- Assert.assertNotNull(rocket);
- Assert.assertEquals("FooBar Test", doc.getRocket().getName());
- Assert.assertTrue(loader.getWarnings().isEmpty());
-
- stream = this.getClass().getResourceAsStream("rocksimTestRocket2.rkt");
- Assert.assertNotNull("Could not open rocksimTestRocket2.rkt", stream);
-
- doc = OpenRocketDocumentFactory.createEmptyRocket();
- DocumentLoadingContext context = new DocumentLoadingContext();
- context.setOpenRocketDocument(doc);
- context.setMotorFinder(new DatabaseMotorFinder());
- loader.loadFromStream(context, new BufferedInputStream(stream));
-
- Assert.assertNotNull(doc);
- rocket = doc.getRocket();
- Assert.assertNotNull(rocket);
-
- //Do some simple asserts; the important thing here is just validating that the mass and cg were
- //not overridden for each stage.
- Assert.assertEquals("Three Stage Everything Included Rocket", doc.getRocket().getName());
- Assert.assertEquals(0, loader.getWarnings().size());
- Assert.assertEquals(3, rocket.getStageCount());
- Stage stage1 = (Stage) rocket.getChild(0);
- Assert.assertFalse(stage1.isMassOverridden());
- Assert.assertFalse(stage1.isCGOverridden());
- Stage stage2 = (Stage) rocket.getChild(1);
- Assert.assertFalse(stage2.isMassOverridden());
- Assert.assertFalse(stage2.isCGOverridden());
- Stage stage3 = (Stage) rocket.getChild(2);
- Assert.assertFalse(stage3.isMassOverridden());
- Assert.assertFalse(stage3.isCGOverridden());
-
- stream = this.getClass().getResourceAsStream("rocksimTestRocket3.rkt");
- Assert.assertNotNull("Could not open rocksimTestRocket3.rkt", stream);
-
- doc = OpenRocketDocumentFactory.createEmptyRocket();
- context = new DocumentLoadingContext();
- context.setOpenRocketDocument(doc);
- context.setMotorFinder(new DatabaseMotorFinder());
- loader.loadFromStream(context, new BufferedInputStream(stream));
-
- Assert.assertNotNull(doc);
- rocket = doc.getRocket();
- Assert.assertNotNull(rocket);
- Assert.assertEquals("Three Stage Everything Included Rocket - Override Total Mass/CG", doc.getRocket().getName());
- Assert.assertEquals(3, rocket.getStageCount());
- stage1 = (Stage) rocket.getChild(0);
- stage2 = (Stage) rocket.getChild(1);
- stage3 = (Stage) rocket.getChild(2);
-
- //Do some 1st level and simple asserts; the idea here is to not do a deep validation as that
- //should have been covered elsewhere. Assert that the stage overrides are correct.
- Assert.assertEquals(2, stage1.getChildCount());
- Assert.assertEquals("Nose cone", stage1.getChild(0).getName());
- Assert.assertEquals("Body tube", stage1.getChild(1).getName());
- Assert.assertTrue(stage1.isMassOverridden());
- Assert.assertEquals(0.185d, stage1.getOverrideMass(), 0.001);
- Assert.assertTrue(stage1.isCGOverridden());
- Assert.assertEquals(0.3d, stage1.getOverrideCG().x, 0.001);
- Assert.assertEquals(4, loader.getWarnings().size());
-
- Assert.assertEquals(1, stage2.getChildCount());
- Assert.assertEquals("2nd Stage Tube", stage2.getChild(0).getName());
- Assert.assertTrue(stage2.isMassOverridden());
- Assert.assertEquals(0.21d, stage2.getOverrideMass(), 0.001);
- Assert.assertTrue(stage2.isCGOverridden());
- Assert.assertEquals(0.4d, stage2.getOverrideCG().x, 0.001);
-
- BodyTube bt = (BodyTube) stage2.getChild(0);
- LaunchLug ll = (LaunchLug) bt.getChild(6);
- Assert.assertEquals(1.22d, ll.getRadialDirection(), 0.001);
-
- Assert.assertEquals(2, stage3.getChildCount());
- Assert.assertEquals("Transition", stage3.getChild(0).getName());
- Assert.assertEquals("Body tube", stage3.getChild(1).getName());
- Assert.assertTrue(stage2.isMassOverridden());
- Assert.assertEquals(0.33d, stage3.getOverrideMass(), 0.001);
- Assert.assertTrue(stage2.isCGOverridden());
- Assert.assertEquals(0.5d, stage3.getOverrideCG().x, 0.001);
- }
-
- public static OpenRocketDocument loadRocksimRocket(RocksimLoader theLoader) throws IOException, RocketLoadException {
- InputStream stream = RocksimLoaderTest.class.getResourceAsStream("rocksimTestRocket1.rkt");
- try {
- Assert.assertNotNull("Could not open rocksimTestRocket1.rkt", stream);
- OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
- DocumentLoadingContext context = new DocumentLoadingContext();
- context.setOpenRocketDocument(doc);
- context.setMotorFinder(new DatabaseMotorFinder());
- theLoader.loadFromStream(context, new BufferedInputStream(stream));
- return doc;
- } finally {
- stream.close();
- }
- }
-
- public static OpenRocketDocument loadRocksimRocket3(RocksimLoader theLoader) throws IOException, RocketLoadException {
- InputStream stream = RocksimLoaderTest.class.getResourceAsStream("rocksimTestRocket3.rkt");
- try {
- Assert.assertNotNull("Could not open rocksimTestRocket3.rkt", stream);
- OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
- DocumentLoadingContext context = new DocumentLoadingContext();
- context.setOpenRocketDocument(doc);
- context.setMotorFinder(new DatabaseMotorFinder());
- theLoader.loadFromStream(context, new BufferedInputStream(stream));
- return doc;
- } finally {
- stream.close();
- }
- }
-
+
+ /**
+ * Test a bug reported via automated bug report. I have been unable to reproduce this bug (hanging finset off of an inner body tube) when creating
+ * a Rocksim file using Rocksim. The bug is reproducible when manually modifying the Rocksim file, which is what is tested here.
+ */
+ @org.junit.Test
+ public void testFinsOnInnerTube() throws Exception {
+ RocksimLoader loader = new RocksimLoader();
+ InputStream stream = this.getClass().getResourceAsStream("PodFins.rkt");
+ Assert.assertNotNull("Could not open PodFins.rkt", stream);
+ try {
+ OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ loader.loadFromStream(context, new BufferedInputStream(stream));
+ Rocket rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+ }
+ catch (IllegalStateException ise) {
+ Assert.fail(ise.getMessage());
+ }
+ Assert.assertTrue(loader.getWarnings().size() == 2);
+ }
+
+ /**
+ * Method: loadFromStream(InputStream source)
+ *
+ * @throws Exception thrown if something goes awry
+ */
+ @org.junit.Test
+ public void testLoadFromStream() throws Exception {
+ RocksimLoader loader = new RocksimLoader();
+ //Stupid single stage rocket
+ OpenRocketDocument doc = loadRocksimRocket(loader);
+ InputStream stream;
+
+ Assert.assertNotNull(doc);
+ Rocket rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+ Assert.assertEquals("FooBar Test", doc.getRocket().getName());
+ Assert.assertTrue(loader.getWarnings().isEmpty());
+
+ stream = this.getClass().getResourceAsStream("rocksimTestRocket2.rkt");
+ Assert.assertNotNull("Could not open rocksimTestRocket2.rkt", stream);
+
+ doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ loader.loadFromStream(context, new BufferedInputStream(stream));
+
+ Assert.assertNotNull(doc);
+ rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+
+ //Do some simple asserts; the important thing here is just validating that the mass and cg were
+ //not overridden for each stage.
+ Assert.assertEquals("Three Stage Everything Included Rocket", doc.getRocket().getName());
+ Assert.assertEquals(0, loader.getWarnings().size());
+ Assert.assertEquals(3, rocket.getStageCount());
+ Stage stage1 = (Stage) rocket.getChild(0);
+ Assert.assertFalse(stage1.isMassOverridden());
+ Assert.assertFalse(stage1.isCGOverridden());
+ Stage stage2 = (Stage) rocket.getChild(1);
+ Assert.assertFalse(stage2.isMassOverridden());
+ Assert.assertFalse(stage2.isCGOverridden());
+ Stage stage3 = (Stage) rocket.getChild(2);
+ Assert.assertFalse(stage3.isMassOverridden());
+ Assert.assertFalse(stage3.isCGOverridden());
+
+ stream = this.getClass().getResourceAsStream("rocksimTestRocket3.rkt");
+ Assert.assertNotNull("Could not open rocksimTestRocket3.rkt", stream);
+
+ doc = OpenRocketDocumentFactory.createEmptyRocket();
+ context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ loader.loadFromStream(context, new BufferedInputStream(stream));
+
+ Assert.assertNotNull(doc);
+ rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+ Assert.assertEquals("Three Stage Everything Included Rocket - Override Total Mass/CG", doc.getRocket().getName());
+ Assert.assertEquals(3, rocket.getStageCount());
+ stage1 = (Stage) rocket.getChild(0);
+ stage2 = (Stage) rocket.getChild(1);
+ stage3 = (Stage) rocket.getChild(2);
+
+ //Do some 1st level and simple asserts; the idea here is to not do a deep validation as that
+ //should have been covered elsewhere. Assert that the stage overrides are correct.
+ Assert.assertEquals(2, stage1.getChildCount());
+ Assert.assertEquals("Nose cone", stage1.getChild(0).getName());
+ Assert.assertEquals("Body tube", stage1.getChild(1).getName());
+ Assert.assertTrue(stage1.isMassOverridden());
+ Assert.assertEquals(0.185d, stage1.getOverrideMass(), 0.001);
+ Assert.assertTrue(stage1.isCGOverridden());
+ Assert.assertEquals(0.3d, stage1.getOverrideCG().x, 0.001);
+ Assert.assertEquals(4, loader.getWarnings().size());
+
+ Assert.assertEquals(1, stage2.getChildCount());
+ Assert.assertEquals("2nd Stage Tube", stage2.getChild(0).getName());
+ Assert.assertTrue(stage2.isMassOverridden());
+ Assert.assertEquals(0.21d, stage2.getOverrideMass(), 0.001);
+ Assert.assertTrue(stage2.isCGOverridden());
+ Assert.assertEquals(0.4d, stage2.getOverrideCG().x, 0.001);
+
+ BodyTube bt = (BodyTube) stage2.getChild(0);
+ LaunchLug ll = (LaunchLug) bt.getChild(6);
+ Assert.assertEquals(1.22d, ll.getRadialDirection(), 0.001);
+
+ Assert.assertEquals(2, stage3.getChildCount());
+ Assert.assertEquals("Transition", stage3.getChild(0).getName());
+ Assert.assertEquals("Body tube", stage3.getChild(1).getName());
+ Assert.assertTrue(stage2.isMassOverridden());
+ Assert.assertEquals(0.33d, stage3.getOverrideMass(), 0.001);
+ Assert.assertTrue(stage2.isCGOverridden());
+ Assert.assertEquals(0.5d, stage3.getOverrideCG().x, 0.001);
+ }
+
+ @org.junit.Test
+ public void testSubAssemblyRocket() throws IOException, RocketLoadException {
+ RocksimLoader loader = new RocksimLoader();
+ //Stupid single stage rocket
+ OpenRocketDocument doc = loadRocksimSubassemblyRocket(loader);
+ InputStream stream;
+
+ Assert.assertNotNull(doc);
+ Rocket rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+ Assert.assertEquals("SubAssembly Element Test", doc.getRocket().getName());
+ Assert.assertTrue(loader.getWarnings().isEmpty());
+
+ stream = this.getClass().getResourceAsStream("SubAssemblyTest.rkt");
+ Assert.assertNotNull("Could not open SubAssemblyTest.rkt", stream);
+
+ doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ loader.loadFromStream(context, new BufferedInputStream(stream));
+
+ Assert.assertNotNull(doc);
+ rocket = doc.getRocket();
+ Assert.assertNotNull(rocket);
+ Assert.assertEquals(1, rocket.getStageCount());
+ Stage stage1 = (Stage) rocket.getChild(0);
+ Assert.assertEquals("Nose cone", stage1.getChild(0).getName());
+ Assert.assertEquals("Forward Body tube", stage1.getChild(1).getName());
+ Assert.assertEquals("Aft Body tube", stage1.getChild(2).getName());
+
+ BodyTube subassemblyBodyTube = (BodyTube)stage1.getChild(2);
+ Assert.assertEquals(8, subassemblyBodyTube.getChildCount());
+ Assert.assertEquals("Engine block", subassemblyBodyTube.getChild(0).getName());
+ Assert.assertEquals("Fin set-1", subassemblyBodyTube.getChild(1).getName());
+ Assert.assertEquals("Fin set", subassemblyBodyTube.getChild(2).getName());
+ Assert.assertEquals("Fin set-2", subassemblyBodyTube.getChild(3).getName());
+ Assert.assertEquals("Fin set-3", subassemblyBodyTube.getChild(4).getName());
+ Assert.assertEquals("Fin set-4", subassemblyBodyTube.getChild(5).getName());
+ Assert.assertEquals("Centering ring", subassemblyBodyTube.getChild(6).getName());
+ Assert.assertEquals("Centering ring", subassemblyBodyTube.getChild(7).getName());
+ }
+
+ public static OpenRocketDocument loadRocksimRocket(RocksimLoader theLoader) throws IOException, RocketLoadException {
+ InputStream stream = RocksimLoaderTest.class.getResourceAsStream("rocksimTestRocket1.rkt");
+ try {
+ Assert.assertNotNull("Could not open rocksimTestRocket1.rkt", stream);
+ OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ theLoader.loadFromStream(context, new BufferedInputStream(stream));
+ return doc;
+ }
+ finally {
+ stream.close();
+ }
+ }
+
+ public static OpenRocketDocument loadRocksimRocket3(RocksimLoader theLoader) throws IOException, RocketLoadException {
+ InputStream stream = RocksimLoaderTest.class.getResourceAsStream("rocksimTestRocket3.rkt");
+ try {
+ Assert.assertNotNull("Could not open rocksimTestRocket3.rkt", stream);
+ OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ theLoader.loadFromStream(context, new BufferedInputStream(stream));
+ return doc;
+ }
+ finally {
+ stream.close();
+ }
+ }
+
+ public static OpenRocketDocument loadRocksimSubassemblyRocket(RocksimLoader theLoader) throws IOException, RocketLoadException {
+ InputStream stream = RocksimLoaderTest.class.getResourceAsStream("SubAssemblyTest.rkt");
+ try {
+ Assert.assertNotNull("Could not open SubAssemblyTest.rkt", stream);
+ OpenRocketDocument doc = OpenRocketDocumentFactory.createEmptyRocket();
+ DocumentLoadingContext context = new DocumentLoadingContext();
+ context.setOpenRocketDocument(doc);
+ context.setMotorFinder(new DatabaseMotorFinder());
+ theLoader.loadFromStream(context, new BufferedInputStream(stream));
+ return doc;
+ }
+ finally {
+ stream.close();
+ }
+ }
+
}
diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/SubAssemblyTest.rkt b/core/test/net/sf/openrocket/file/rocksim/importt/SubAssemblyTest.rkt
new file mode 100644
index 000000000..7bb84f148
--- /dev/null
+++ b/core/test/net/sf/openrocket/file/rocksim/importt/SubAssemblyTest.rkt
@@ -0,0 +1,483 @@
+
+ 4
+
+
+ SubAssembly Element Test
+ 1
+ 7
+ 0
+ 3
+ 0
+ 3
+ 0.0
+ 0.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 18
+ 0.0
+ 0.0
+ 0.0
+ 426.6301716552979
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 1
+ 0
+
+
+ 6.107306539734526
+ 1049.21
+ Polystyrene PS
+ Nose cone
+ 0.0
+ 0
+ 0.0
+ 6.107306539734526
+ 53.68748632541546
+ 0
+ 0.0
+ 0.0
+ 0
+ 76.2
+ 0
+ 1
+ 3
+ 1
+ 1.00076
+ 0.0
+
+ 24.8006
+ 19.1008
+ 23.5991
+
+
+ 15.040422946044567
+ 1121.29
+ Paper
+ Forward Body tube
+ 0.0
+ 0
+ 0.0
+ 15.040422946044567
+ 228.6
+ 0
+ 0.0
+ 0.0
+ 0
+ 457.2
+ 0
+ 2
+ 24.892
+ 24.13
+ 0
+ 24.13
+ 0.0
+ 0
+
+
+ 0.0
+ 1121.29
+ Paper
+ Tube coupler
+ 0.0
+ 0
+ 120.65
+ 0.0
+ 31.75
+ 0
+ 0.0
+ 0.0
+ 0
+ 63.5
+ 0
+ 3
+ 27.8892
+ 27.8892
+ 4
+ 0
+
+
+ 10.038912051168
+ 0.00664972
+ Mylar
+ Streamer
+ 0.0
+ 0
+ 44.45
+ 10.038912051168
+ 57.15
+ 1
+ 0.0
+ 0.0
+ 0
+ 114.3
+ 0
+ 4
+ 114.3
+ 0.138
+
+
+ 1.2996672000000002
+ 0.0
+
+ Shock Cord
+ 187.325
+ 1
+ 187.325
+ 0.0
+ 0.0
+ 0
+ 0.0
+ 0.0
+ 0
+ 12.192000000000002
+ 0
+ 5
+ 0
+
+
+ 2.549
+ 0.0
+
+ Mass object - 2.549 g
+ 282.575
+ 1
+ 282.575
+ 0.0
+ 0.0
+ 0
+ 0.0
+ 0.0
+ 0
+ 0.0
+ 0
+ 6
+ 0
+
+
+ 1.1277286611933457
+ 1121.29
+ Paper
+ Centering ring
+ 0.0
+ 0
+ 384.175
+ 1.1277286611933457
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 0
+ 6.35
+ 0
+ 7
+ 28.702
+ 24.9428
+ 0
+ 0
+
+
+ 1.1277286611933457
+ 1121.29
+ Paper
+ Centering ring
+ 0.0
+ 0
+ 358.775
+ 1.1277286611933457
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 0
+ 6.35
+ 0
+ 8
+ 28.702
+ 24.9428
+ 0
+ 0
+
+
+ 1.1277286611933457
+ 1121.29
+ Paper
+ Centering ring
+ 0.0
+ 0
+ 288.925
+ 1.1277286611933457
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 0
+ 6.35
+ 0
+ 9
+ 28.702
+ 24.9428
+ 0
+ 0
+
+
+
+
+ 13.498613632777841
+ 1121.29
+ Paper
+ Aft Body tube
+ 0.0
+ 0
+ 0.0
+ 13.498613632777841
+ 165.1
+ 0
+ 0.0
+ 0.0
+ 0
+ 330.2
+ 0
+ 10
+ 29.8704
+ 29.083
+ 1
+ 29.083
+ 12.7
+ 0
+
+
+ 1.25084668869137
+ 1121.29
+ Paper
+ Engine block
+ 0.0
+ 0
+ 252.73000000000002
+ 1.25084668869137
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 0
+ 6.35
+ 0
+ 11
+ 29.083
+ 24.9428
+ 2
+ 0
+
+
+ 0.9977479979838839
+ 128.148
+ Balsa
+ Fin set-1
+ 0.0
+ 0
+ 257.175
+ 0.9977479979838839
+ 54.72939060773481
+ 0
+ 0.0
+ 0.0
+ 0
+ 0.0
+ 0
+ 12
+ 1
+ 66.675
+ 29.8704
+ 50.8
+ 67.4141
+ 3.175
+ 0
+ 0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 0.0
+
+
+ 2.8874580315239995
+ 128.148
+ Balsa
+ Fin set
+ 0.0
+ 0
+ 146.05
+ 2.8874580315239995
+ 61.383333333333326
+ 0
+ 0.0
+ 0.785398
+ 0
+ 0.0
+ 0
+ 13
+ 4
+ 101.6
+ 0.0
+ 34.925
+ 82.55
+ 3.175
+ 0
+ 0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 0.0
+
+
+ 0.9977479979838839
+ 128.148
+ Balsa
+ Fin set-2
+ 0.0
+ 0
+ 257.175
+ 0.9977479979838839
+ 54.72939060773481
+ 0
+ 0.0
+ 1.5708
+ 0
+ 0.0
+ 0
+ 14
+ 1
+ 66.675
+ 29.8704
+ 50.8
+ 67.4141
+ 3.175
+ 0
+ 0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 0.0
+
+
+ 0.9977479979838839
+ 128.148
+ Balsa
+ Fin set-3
+ 0.0
+ 0
+ 257.175
+ 0.9977479979838839
+ 54.72939060773481
+ 0
+ 0.0
+ 3.14159
+ 0
+ 0.0
+ 0
+ 15
+ 1
+ 66.675
+ 29.8704
+ 50.8
+ 67.4141
+ 3.175
+ 0
+ 0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 0.0
+
+
+ 0.9977479979838839
+ 128.148
+ Balsa
+ Fin set-4
+ 0.0
+ 0
+ 257.175
+ 0.9977479979838839
+ 54.72939060773481
+ 0
+ 0.0
+ -1.5708
+ 0
+ 0.0
+ 0
+ 16
+ 1
+ 66.675
+ 29.8704
+ 50.8
+ 67.4141
+ 3.175
+ 0
+ 0
+ 0.0
+ 0.0
+ 0.0
+ 1
+ 0.0
+
+
+ 1.1277286611933457
+ 1121.29
+ Paper
+ Centering ring
+ 0.0
+ 0
+ 323.85
+ 1.1277286611933457
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 2
+ 6.35
+ 0
+ 17
+ 28.702
+ 24.9428
+ 0
+ 0
+
+
+ 1.1277286611933457
+ 1121.29
+ Paper
+ Centering ring
+ 0.0
+ 0
+ 282.575
+ 1.1277286611933457
+ 3.175
+ 0
+ 0.0
+ 0.0
+ 2
+ 6.35
+ 0
+ 18
+ 28.702
+ 24.9428
+ 0
+ 0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/web/html/actions/updates.php b/core/web/html/actions/updates.php
index ddbde5228..3d6f337e7 100644
--- a/core/web/html/actions/updates.php
+++ b/core/web/html/actions/updates.php
@@ -80,11 +80,32 @@ header("Content-type: text/plain");
$version = $_GET["version"];
$updates = "";
-$unstable = "14.03";
+$unstable = "14.11";
$stable = "1.0.0";
-if (preg_match("/^13.11.2$/", $version)) {
+if (preg_match("/^14.06$/", $version)) {
+ $updates = "Version: " . $unstable . "\n" .
+ "6: Updated thrustcurves\n" .
+ "4: Fixed a few bugs\n" .
+ "";
+} else if (preg_match("/^14.05$/", $version)) {
+ $updates = "Version: " . $unstable . "\n" .
+ "10: Added Knots to windspeed and velocity units\n" .
+ "6: Updated thrustcurves\n" .
+ "4: Added Klima motor texture\n" .
+ "4: Fixed annoying table bug\n" .
+ "";
+} else if (preg_match("/^14.03$/", $version)) {
+ $updates = "Version: " . $unstable . "\n" .
+ "10: Simulations will now compute the optimum delay for the sustainer\n" .
+ "10: CG and Mass overrides are now indicated in the component tree\n" .
+ "6: Updated thrustcurves - added Klima and SCR motors and various others\n" .
+ "4: Removed the preset component Estes PNC-80FB since it was never produced\n" .
+ "4: Updated 3D libraries\n" .
+ "4: Bug fixes in motor selection dialog\n".
+ "";
+} else if (preg_match("/^13.11.2$/", $version)) {
$updates = "Version: " . $unstable . "\n" .
"10: Realistic in flight 3D photo construction\n" .
"6: Updated thrustcurves\n".
diff --git a/core/web/html/contact.html b/core/web/html/contact.html
index ea3bb212d..ea0eab206 100644
--- a/core/web/html/contact.html
+++ b/core/web/html/contact.html
@@ -5,7 +5,7 @@
-
+
+
02.11.2014: Version 14.11 is
+ released! This release contains very few changes. Thrustcurves have been updated and now include AeroTech DMS motors. A few bugs have been fixed.
+
+
+
25.06.2014: Version 14.06 is
+ released! This release contains very minimal changes. It fixes an annoying bug in tables that many people experience. A texture is added for Klima motors.
+
+
+
21.05.2014: Version 14.05 is
+ released! This release contains some minor features,
+ bug fixes and updated thrust curves. New motor vendors Klima and Southern
+ Cross Rocketry are included. The simulation will now compute optimum delay based
+ on actual coast time. Components with CG and mass overrides are decorated with
+ an icon in the component tree.
+
21.03.2014: Version 14.03 is
released! This release contains the
@@ -16,6 +31,8 @@
and motor selection windows as well.
+
+
01.01.2014: Version 13.11.2 is
released! This release contains bug fixes,
usability improvements, and layout fixes.
@@ -34,9 +51,6 @@
Chineese translations have been added and Russian translations updated.
Updated the 3D libraries, and squashed some bugs introduced by Java 1.7.0_45-b18.
-
-
-
6.10.2013: Version 13.09.1 is
released! This is a bug-fix release,
and includes updated 3D libraries and new thrust curves for
diff --git a/swing/build.xml b/swing/build.xml
index 5b5c4408e..0256084b9 100644
--- a/swing/build.xml
+++ b/swing/build.xml
@@ -27,7 +27,7 @@
-
+
diff --git a/swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar b/swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar
index 8a565c890..f0d035cc8 100644
Binary files a/swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar and b/swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar differ
diff --git a/swing/lib/jogl/gluegen-rt-natives-linux-i586.jar b/swing/lib/jogl/gluegen-rt-natives-linux-i586.jar
index 73fef9804..d9f789ec3 100644
Binary files a/swing/lib/jogl/gluegen-rt-natives-linux-i586.jar and b/swing/lib/jogl/gluegen-rt-natives-linux-i586.jar differ
diff --git a/swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar b/swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar
index a8ea24f06..130731720 100644
Binary files a/swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar and b/swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar differ
diff --git a/swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar b/swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar
index 8be2607e4..6c5b925b2 100644
Binary files a/swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar and b/swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar differ
diff --git a/swing/lib/jogl/gluegen-rt-natives-windows-i586.jar b/swing/lib/jogl/gluegen-rt-natives-windows-i586.jar
index d9f263429..814c3b99d 100644
Binary files a/swing/lib/jogl/gluegen-rt-natives-windows-i586.jar and b/swing/lib/jogl/gluegen-rt-natives-windows-i586.jar differ
diff --git a/swing/lib/jogl/gluegen-rt.jar b/swing/lib/jogl/gluegen-rt.jar
index ee7287ae6..c20455ec7 100644
Binary files a/swing/lib/jogl/gluegen-rt.jar and b/swing/lib/jogl/gluegen-rt.jar differ
diff --git a/swing/lib/jogl/jogl-all-natives-linux-amd64.jar b/swing/lib/jogl/jogl-all-natives-linux-amd64.jar
index 823cb352e..9bbc12c09 100644
Binary files a/swing/lib/jogl/jogl-all-natives-linux-amd64.jar and b/swing/lib/jogl/jogl-all-natives-linux-amd64.jar differ
diff --git a/swing/lib/jogl/jogl-all-natives-linux-i586.jar b/swing/lib/jogl/jogl-all-natives-linux-i586.jar
index 73da07f48..f3e92cd63 100644
Binary files a/swing/lib/jogl/jogl-all-natives-linux-i586.jar and b/swing/lib/jogl/jogl-all-natives-linux-i586.jar differ
diff --git a/swing/lib/jogl/jogl-all-natives-macosx-universal.jar b/swing/lib/jogl/jogl-all-natives-macosx-universal.jar
index 0ff432d97..35f72f55a 100644
Binary files a/swing/lib/jogl/jogl-all-natives-macosx-universal.jar and b/swing/lib/jogl/jogl-all-natives-macosx-universal.jar differ
diff --git a/swing/lib/jogl/jogl-all-natives-windows-amd64.jar b/swing/lib/jogl/jogl-all-natives-windows-amd64.jar
index ebfab4f05..314434879 100644
Binary files a/swing/lib/jogl/jogl-all-natives-windows-amd64.jar and b/swing/lib/jogl/jogl-all-natives-windows-amd64.jar differ
diff --git a/swing/lib/jogl/jogl-all-natives-windows-i586.jar b/swing/lib/jogl/jogl-all-natives-windows-i586.jar
index dafaae992..f49d9ce19 100644
Binary files a/swing/lib/jogl/jogl-all-natives-windows-i586.jar and b/swing/lib/jogl/jogl-all-natives-windows-i586.jar differ
diff --git a/swing/lib/jogl/jogl-all.jar b/swing/lib/jogl/jogl-all.jar
index d86116ca0..1d048da6b 100644
Binary files a/swing/lib/jogl/jogl-all.jar and b/swing/lib/jogl/jogl-all.jar differ
diff --git a/swing/resources-src/datafiles/presets/Estes.orc b/swing/resources-src/datafiles/presets/Estes.orc
index 47e793998..839719b71 100644
--- a/swing/resources-src/datafiles/presets/Estes.orc
+++ b/swing/resources-src/datafiles/presets/Estes.orc
@@ -1565,19 +1565,6 @@
PNC-80BBPlastic Nose conePolystyrene PS
- 0.03685438003
- PARABOLIC
- 0.06604
- 0.0649732
- 0.044449999999999996
- 0.1016
- 0.003175
-
-
- Estes
- PNC-80FB
- Estes PNC-80 Fat Boy profile cone
- Polystyrene PS0.035892ELLIPSOID0.06604
diff --git a/swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV
index 7b6204894..7b38bcd19 100644
--- a/swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV
+++ b/swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV
@@ -26,7 +26,6 @@ Estes,PNC-60L ,Plastic Nose cone,in,2.5,1.637,0,0.75,1.595,0.125,elliptical,holl
Estes,PNC-60MS ,Plastic Nose cone,in,2.5,1.637,0,0.75,1.595,0.125,parabolic,hollow,Polystyrene PS,1.625,oz,0.5,0,,,,,,,,,,,,,
Estes,PNC-60NA,Plastic Nose cone,in,4.75,1.637,0,0.75,1.595,0.125,ogive,hollow,Polystyrene PS,0,oz,0.6,0,,,,,,,,,,,,,
Estes,BNC-70AJ ,Balsa Nose cone,in,4.4,2.217,0,0.75,2.18,0,parabolic,solid,Balsa,2.575,oz,0.85,0,,,,,,,,,,,,,
-Estes,PNC-80BB ,Plastic Nose cone,in,4,2.6,0,1.75,2.558,0.125,parabolic,hollow,Polystyrene PS,2.875,oz,1.3,0,,,,,,,,,,,,,
-Estes,PNC-80FB,Estes PNC-80 Fat Boy profile cone,mm,101.6,66.04,0,38.1,64.922,3.175,elliptical,hollow,Polystyrene PS,0,g,35.892,0,,,,,,,,,,,,,
+Estes,PNC-80BB,Plastic Nose cone,mm,101.6,66.04,0,38.1,64.922,3.175,elliptical,hollow,Polystyrene PS,0,g,35.892,0,,,,,,,,,,,,,
Estes,PNC-80K ,Plastic Nose cone,in,8.15,2.6,0,1,2.558,0.125,ogive,hollow,Polystyrene PS,4.575,oz,1.68,0,,,,,,,,,,,,,
Estes,PNC-55AO,Elliptical,mm,127,33.655,0,15.24,32.588,2.3876,elliptical,hollow,Polystyrene PS,100.99,g,12.19,0,,,,,,,,,,,,,
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/resources/datafiles/presets/system.ser b/swing/resources/datafiles/presets/system.ser
index 404e4735a..5342b5a8d 100644
Binary files a/swing/resources/datafiles/presets/system.ser and b/swing/resources/datafiles/presets/system.ser differ
diff --git a/swing/resources/datafiles/textures/motors/klima.jpg b/swing/resources/datafiles/textures/motors/klima.jpg
new file mode 100644
index 000000000..c5be2761c
Binary files /dev/null and b/swing/resources/datafiles/textures/motors/klima.jpg 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..f0e937345
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java
@@ -0,0 +1,31 @@
+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);
+ if ( index < 0 ) {
+ return null;
+ }
+ 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/configdialog/MassComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java
index cf70d463f..b4e277192 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java
@@ -32,7 +32,22 @@ public class MassComponentConfig extends RocketComponentConfig {
JPanel panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
+ //// Mass component type
+ panel.add(new JLabel(trans.get("MassComponentCfg.lbl.type")));
+ @SuppressWarnings("unchecked")
+ JComboBox typecombo = new JComboBox(
+ new EnumModel(component, "MassComponentType",
+ new MassComponent.MassComponentType[] {
+ MassComponent.MassComponentType.MASSCOMPONENT,
+ MassComponent.MassComponentType.ALTIMETER,
+ MassComponent.MassComponentType.FLIGHTCOMPUTER,
+ MassComponent.MassComponentType.DEPLOYMENTCHARGE,
+ MassComponent.MassComponentType.TRACKER,
+ MassComponent.MassComponentType.PAYLOAD,
+ MassComponent.MassComponentType.RECOVERYHARDWARE,
+ MassComponent.MassComponentType.BATTERY}));
+ panel.add(typecombo, "spanx, growx, wrap");
//// Mass
panel.add(new JLabel(trans.get("MassComponentCfg.lbl.Mass")));
@@ -116,6 +131,7 @@ public class MassComponentConfig extends RocketComponentConfig {
new DoubleModel(component.getParent(), "Length"))),
"w 100lp, wrap");
+
//// General and General properties
tabbedPane.insertTab(trans.get("MassComponentCfg.tab.General"), null, panel,
trans.get("MassComponentCfg.tab.ttip.General"), 0);
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/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
index ff9de619f..7ea640b54 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
@@ -310,6 +310,10 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
}
public void setMotorMountAndConfig( MotorMount mount, String currentConfig ) {
+ selectedMotor = null;
+ selectedMotorSet = null;
+ selectedDelay = 0;
+
ThrustCurveMotor motorToSelect = null;
if (currentConfig != null && mount != null) {
MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig);
@@ -317,21 +321,14 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
selectedDelay = motorConf.getEjectionDelay();
}
- selectedMotorSet = null;
-
// If current motor is not found in db, add a new ThrustCurveMotorSet containing it
if (motorToSelect != null) {
- for (ThrustCurveMotorSet motorSet : database) {
- if (motorSet.getMotors().contains(motorToSelect)) {
- selectedMotorSet = motorSet;
- break;
- }
- }
- if (selectedMotorSet == null) {
+ ThrustCurveMotorSet motorSetToSelect = null;
+ motorSetToSelect = findMotorSet(motorToSelect);
+ if (motorSetToSelect == null) {
database = new ArrayList(database);
ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
extra.addMotor(motorToSelect);
- selectedMotorSet = extra;
database.add(extra);
Collections.sort(database);
}
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/dialogs/preferences/PreferencesDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
index c76ad6fe0..5fdfa4f60 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
@@ -164,7 +164,7 @@ public class PreferencesDialog extends JDialog {
//// Position to insert new body components:
panel.add(new JLabel(trans.get("pref.dlg.lbl.Positiontoinsert")), "gapright para");
- panel.add(new JComboBox(new PrefChoiseSelector(Preferences.BODY_COMPONENT_INSERT_POSITION_KEY,
+ panel.add(new JComboBox(new PrefChoiceSelector(Preferences.BODY_COMPONENT_INSERT_POSITION_KEY,
//// Always ask
//// Insert in middle
//// Add to end
@@ -178,8 +178,18 @@ public class PreferencesDialog extends JDialog {
//// Delete
//// Confirm
trans.get("pref.dlg.PrefBooleanSelector1"),
- trans.get("pref.dlg.PrefBooleanSelector2"), true)), "wrap 40lp, growx, sg combos");
-
+ trans.get("pref.dlg.PrefBooleanSelector2"), true)), "wrap, growx, sg combos");
+ //// Position to insert new body components:
+ panel.add(new JLabel(trans.get("pref.dlg.lbl.Rocketinfofontsize")), "gapright para");
+
+ panel.add(new JComboBox(new PrefChoiceSelector(Preferences.ROCKET_INFO_FONT_SIZE,
+ //// Small
+ //// Medium
+ //// Large
+ trans.get("pref.dlg.PrefFontSmall"),
+ trans.get("pref.dlg.PrefFontMedium"),
+ trans.get("pref.dlg.PrefFontLarge"))), "wrap 40lp, growx, sg combos");
+
//// User-defined thrust curves:
panel.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap");
final JTextField field = new JTextField();
@@ -285,6 +295,7 @@ public class PreferencesDialog extends JDialog {
+
//// Check for software updates at startup
final JCheckBox softwareUpdateBox =
new JCheckBox(trans.get("pref.dlg.checkbox.Checkupdates"));
@@ -317,8 +328,32 @@ public class PreferencesDialog extends JDialog {
preferences.setAutoOpenLastDesignOnStartup(autoOpenDesignFile.isSelected());
}
});
- panel.add(autoOpenDesignFile);
+ panel.add(autoOpenDesignFile, "wrap, growx, span 2");
+ //// Automatically run all simulation out-dated by design changes.
+ final JCheckBox automaticallyRunSimsBox =
+ new JCheckBox(trans.get("pref.dlg.checkbox.Runsimulations"));
+ automaticallyRunSimsBox.setSelected(preferences.getAutoRunSimulations());
+ automaticallyRunSimsBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ preferences.setAutoRunSimulations(automaticallyRunSimsBox.isSelected());
+ }
+ });
+ panel.add(automaticallyRunSimsBox, "wrap, growx, sg combos ");
+
+ //// Update flight estimates in the design window
+ final JCheckBox updateEstimates =
+ new JCheckBox(trans.get("pref.dlg.checkbox.Updateestimates"));
+ updateEstimates.setSelected(preferences.computeFlightInBackground());
+ updateEstimates.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ preferences.setComputeFlightInBackground(updateEstimates.isSelected());
+ }
+ });
+ panel.add(updateEstimates, "wrap, growx, sg combos ");
+
return panel;
}
@@ -673,11 +708,11 @@ public class PreferencesDialog extends JDialog {
- private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
+ private class PrefChoiceSelector extends AbstractListModel implements ComboBoxModel {
private final String preference;
private final String[] descriptions;
- public PrefChoiseSelector(String preference, String... descriptions) {
+ public PrefChoiceSelector(String preference, String... descriptions) {
this.preference = preference;
this.descriptions = descriptions;
}
diff --git a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
index 27849cd2f..16f7cf3f2 100644
--- a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
+++ b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
@@ -77,7 +77,9 @@ public class RocketInfo implements FigureElement {
public void paint(Graphics2D myG2, double scale, Rectangle visible) {
this.g2 = myG2;
this.line = FONT.getLineMetrics("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
- myG2.getFontRenderContext()).getHeight();
+ myG2.getFontRenderContext()).getHeight() +
+ FONT.getLineMetrics("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+ myG2.getFontRenderContext()).getDescent();
x1 = visible.x + MARGIN;
x2 = visible.x + visible.width - MARGIN;
@@ -429,11 +431,13 @@ public class RocketInfo implements FigureElement {
private GlyphVector createText(String text) {
- return FONT.createGlyphVector(g2.getFontRenderContext(), text);
+ float size=Application.getPreferences().getRocketInfoFontSize();
+ return (FONT.deriveFont(size)).createGlyphVector(g2.getFontRenderContext(), text);
}
private GlyphVector createSmallText(String text) {
- return SMALLFONT.createGlyphVector(g2.getFontRenderContext(), text);
+ float size=(float) (Application.getPreferences().getRocketInfoFontSize()-2.0);
+ return (SMALLFONT.deriveFont(size)).createGlyphVector(g2.getFontRenderContext(), text);
}
}
diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
index fd8c11272..10fb9e71f 100644
--- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
+++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
@@ -54,6 +54,7 @@ import javax.swing.border.BevelBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
+import javax.swing.event.ChangeEvent;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
@@ -153,7 +154,7 @@ public class BasicFrame extends JFrame {
/** Actions available for rocket modifications */
private final RocketActions actions;
-
+ private SimulationPanel simulationPanel;
/**
@@ -174,7 +175,7 @@ public class BasicFrame extends JFrame {
componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
// Obtain the simulation selection model that will be used
- SimulationPanel simulationPanel = new SimulationPanel(document);
+ simulationPanel = new SimulationPanel(document);
simulationSelectionModel = simulationPanel.getSimulationListSelectionModel();
// Combine into a DocumentSelectionModel
@@ -203,6 +204,11 @@ public class BasicFrame extends JFrame {
//// Flight simulations
tabbedPane.addTab(trans.get("BasicFrame.tab.Flightsim"), null, simulationPanel);
+ // Add change listener to catch when the tabs are changed. This is to run simulations
+ // automagically when the simulation tab is selected.
+ tabbedPane.addChangeListener(new BasicFrame_changeAdapter(this));
+
+
vertical.setTopComponent(tabbedPane);
@@ -1553,4 +1559,24 @@ public class BasicFrame extends JFrame {
return null;
}
}
+
+ public void stateChanged(ChangeEvent e) {
+ JTabbedPane tabSource = (JTabbedPane) e.getSource();
+ String tab = tabSource.getTitleAt(tabSource.getSelectedIndex());
+ if (tab.equals(trans.get("BasicFrame.tab.Flightsim"))) {
+ simulationPanel.activating();
+ }
+ }
}
+
+
+class BasicFrame_changeAdapter implements javax.swing.event.ChangeListener {
+ BasicFrame adaptee;
+
+ BasicFrame_changeAdapter(BasicFrame adaptee) {
+ this.adaptee = adaptee;
+ }
+ public void stateChanged(ChangeEvent e) {
+ adaptee.stateChanged(e);
+ }
+}
\ No newline at end of file
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index 4a238463a..a81436995 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;
@@ -43,11 +44,13 @@ import net.sf.openrocket.gui.simulation.SimulationEditDialog;
import net.sf.openrocket.gui.simulation.SimulationRunDialog;
import net.sf.openrocket.gui.simulation.SimulationWarningDialog;
import net.sf.openrocket.gui.util.Icons;
+import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
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 +62,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 +103,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 +136,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 +153,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 +162,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 +177,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 +217,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 +231,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 +294,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 +318,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 +339,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 +511,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 +523,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 +539,7 @@ public class SimulationPanel extends JPanel {
}
}
});
-
+
document.addDocumentChangeListener(new DocumentChangeListener() {
@Override
public void documentChanged(DocumentChangeEvent event) {
@@ -524,10 +548,10 @@ public class SimulationPanel extends JPanel {
simulationTableModel.fireTableDataChanged();
}
});
-
-
-
-
+
+
+
+
// Fire table change event when the rocket changes
document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
@Override
@@ -535,14 +559,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 +584,51 @@ public class SimulationPanel extends JPanel {
runButton.setEnabled(true);
deleteButton.setEnabled(true);
}
-
+
}
+
+ /// when the simulation tab is selected this run outdated simulated if appropriate.
+ public void activating(){
+ if( ((Preferences) Application.getPreferences()).getAutoRunSimulations()){
+ int nSims = simulationTable.getRowCount();
+ int outdated = 0;
+ if (nSims == 0) {
+ return;
+ }
+
+ for (int i = 0; i < nSims; i++) {
+ Simulation.Status s = document.getSimulation(simulationTable.convertRowIndexToModel(i)).getStatus();
+ if((s==Simulation.Status.NOT_SIMULATED) ||
+ (s==Simulation.Status.OUTDATED)){
+ outdated++;
+ }
+ }
+ if(outdated>0){
+ Simulation[] sims = new Simulation[outdated];
+
+ int index=0;
+ for (int i = 0; i < nSims; i++) {
+ int t = simulationTable.convertRowIndexToModel(i);
+ Simulation s = document.getSimulation(t);
+ if((s.getStatus()==Status.NOT_SIMULATED)||(s.getStatus()==Status.OUTDATED)){
+ sims[index] = s;
+ index++;
+ }
+ }
+ long t = System.currentTimeMillis();
+ new SimulationRunDialog(SwingUtilities.getWindowAncestor(
+ SimulationPanel.this), document, sims).setVisible(true);
+ log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms");
+ fireMaintainSelection();
+ }
+ }
+ }
+
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 +637,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 +645,7 @@ public class SimulationPanel extends JPanel {
}
openDialog(plotMode, sim);
}
-
+
private void fireMaintainSelection() {
int[] selection = simulationTable.getSelectedRows();
simulationTableModel.fireTableDataChanged();
@@ -593,24 +655,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 +681,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/main/componenttree/ComponentTreeModel.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java
index fe141f612..c80b73561 100644
--- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java
+++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java
@@ -138,7 +138,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
@Override
public void componentChanged(ComponentChangeEvent e) {
- if (e.isTreeChange() || e.isUndoChange()) {
+ if (e.isTreeChange() || e.isUndoChange() || e.isMassChange()) {
// Tree must be fully updated also in case of an undo change
fireTreeStructureChanged(e.getSource());
if (e.isTreeChange() && e.isUndoChange()) {
diff --git a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
index de9982b58..2539cc4cb 100644
--- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
@@ -3,17 +3,26 @@ package net.sf.openrocket.gui.main.componenttree;
import java.awt.Component;
+import java.awt.FlowLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
import javax.swing.JTree;
+import javax.swing.UIManager;
import javax.swing.tree.DefaultTreeCellRenderer;
import net.sf.openrocket.gui.main.ComponentIcons;
+import net.sf.openrocket.gui.util.Icons;
+import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.TextUtil;
public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
+ private static final Translator trans = Application.getTranslator();
+
@Override
public Component getTreeCellRendererComponent(
JTree tree,
@@ -24,26 +33,59 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
int row,
boolean hasFocus1) {
- super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus1);
+ Component comp = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus1);
// Set icon
setIcon(ComponentIcons.getSmallIcon(value.getClass()));
- // Set tooltip
RocketComponent c = (RocketComponent) value;
+
+ if ( c.isMassOverridden() || c.isCGOverridden()) {
+ JPanel p = new JPanel();
+ p.setLayout( new FlowLayout( FlowLayout.LEFT, 1,1) );
+ p.setBackground( UIManager.getColor("Tree.textBackground"));
+ p.setForeground( UIManager.getColor("Tree.textForeground"));
+ p.add(comp/*, BorderLayout.WEST*/);
+ if ( c.isMassOverridden() ) {
+ p.add( new JLabel( Icons.MASS_OVERRIDE ) );
+ }
+ if ( c.isCGOverridden() ) {
+ p.add( new JLabel(Icons.CG_OVERRIDE) );
+ }
+ p.setToolTipText(getToolTip(c));
+ comp = p;
+ }
+
+ // Set tooltip
this.setToolTipText(getToolTip(c));
- return this;
+ return comp;
}
- private String getToolTip(RocketComponent c) {
+ private static String getToolTip(RocketComponent c) {
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append("").append(c.getName()).append("");
- if (c.isMassive()) {
- sb.append(" (").append(UnitGroup.UNITS_MASS.toStringUnit(c.getMass())).append(")");
+ if (c.isMassive() || c.isMassOverridden() ) {
+ sb.append(" (").append(UnitGroup.UNITS_MASS.toStringUnit(c.getMass()));
+ if(c.getChildCount()>0){
+ sb.append(" of ").append(UnitGroup.UNITS_MASS.toStringUnit(c.getSectionMass())).append( " total");
+ }
+ sb.append(")");
+ } else {
+ if((c.getChildCount()>0) && (c.getSectionMass()>0)){
+ sb.append(" (").append(UnitGroup.UNITS_MASS.toStringUnit(c.getSectionMass())).append( " total)");
+ }
+ }
+
+ if ( c.isMassOverridden() ) {
+ sb.append(" ").append(trans.get("ComponentTree.ttip.massoverride"));
+ }
+
+ if ( c.isCGOverridden() ) {
+ sb.append(" ").append(trans.get("ComponentTree.ttip.cgoverride"));
}
String comment = c.getComment().trim();
diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
index 3ef4fe226..7360b41e5 100644
--- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
@@ -148,7 +148,7 @@ public abstract class FlightConfigurablePanel= table.getRowCount() || col >= table.getColumnCount() ) {
return null;
}
Object tableValue = table.getModel().getValueAt(row, col);
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));
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java
new file mode 100644
index 000000000..20730e33d
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java
@@ -0,0 +1,237 @@
+package net.sf.openrocket.gui.rocketfigure;
+
+import java.awt.Shape;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.util.Random;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Transformation;
+
+
+public class MassComponentShapes extends RocketComponentShapes {
+
+ public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ net.sf.openrocket.rocketcomponent.MassComponent.MassComponentType type = ((net.sf.openrocket.rocketcomponent.MassComponent)component).getMassComponentType();
+
+ double length = tube.getLength();
+ double radius = tube.getRadius();
+ double arc = Math.min(length, 2*radius) * 0.7;
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S,
+ length*S,2*radius*S,arc*S,arc*S);
+ }
+
+ switch (type) {
+ case ALTIMETER:
+ s = addAltimeterSymbol(s);
+ break;
+ case FLIGHTCOMPUTER:
+ s = addFlightComputerSymbol(s);
+ break;
+ case DEPLOYMENTCHARGE:
+ s = addDeploymentChargeSymbol(s);
+ break;
+ case RECOVERYHARDWARE:
+ s = addRecoveryHardwareSymbol(s);
+ break;
+ case PAYLOAD:
+ s = addPayloadSymbol(s);
+ break;
+ case TRACKER:
+ s = addTrackerSymbol(s);
+ break;
+ case BATTERY:
+ s = addBatterySymbol(s);
+ break;
+ case MASSCOMPONENT:
+ }
+
+ return s;
+ }
+
+
+ public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ double or = tube.getRadius();
+
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S);
+ }
+ return s;
+ }
+
+ private static Shape[] addAltimeterSymbol(Shape[] baseShape){
+ int offset=baseShape.length;
+ Shape[] newShape = new Shape[baseShape.length+1];
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+ Double vMargin = bounds.getHeight()/8.0;
+ Double hMargin = bounds.getWidth()/2.25;
+ Double halfArrowWidth=MathUtil.min(hMargin, vMargin);
+
+ Path2D.Double symbol = new Path2D.Double();
+ symbol.moveTo(bounds.getCenterX(), bounds.getY()+vMargin);
+ symbol.lineTo(bounds.getCenterX(), bounds.getY()+7*vMargin);
+ symbol.lineTo(bounds.getCenterX()-halfArrowWidth, bounds.getY()+6*vMargin);
+ symbol.lineTo(bounds.getCenterX()+halfArrowWidth, bounds.getY()+6*vMargin);
+ symbol.lineTo(bounds.getCenterX(), bounds.getY()+7*vMargin);
+
+ newShape[offset]= symbol;
+ return newShape;
+ }
+
+ private static Shape[] addFlightComputerSymbol(Shape[] baseShape){
+ int pins=12;
+ int offset=baseShape.length;
+ Shape[] newShape = new Shape[baseShape.length+1+pins];
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+
+
+ Double vMargin = bounds.getHeight()/8.0;
+ Double hMargin = bounds.getWidth()/6.0;
+ Double pinHeight=vMargin;
+ Double pinSpacing=(bounds.getWidth()-2*hMargin)/(pins+1);
+ Double pinWidth=pinSpacing/2;
+ newShape[offset]=new Rectangle2D.Double(bounds.getX()+hMargin, bounds.getY()+2*vMargin, 4*hMargin,4*vMargin);
+ for(int i=0; i<(pins/2); i++){
+ newShape[i+1+offset]=new Rectangle2D.Double(bounds.getX()+hMargin+2*i*pinSpacing+pinSpacing, bounds.getY()+6*vMargin, pinWidth, pinHeight);
+ newShape[i+pins/2+1+offset]=new Rectangle2D.Double(bounds.getX()+hMargin+2*i*pinSpacing+pinSpacing, bounds.getY()+vMargin, pinWidth, pinHeight);
+ }
+ //newShape[1]=symbol;
+ return newShape;
+ }
+
+ private static Shape[] addTrackerSymbol(Shape[] baseShape){
+ Shape[] newShape = new Shape[baseShape.length+7];
+ int offset=baseShape.length;
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+ Double vMargin=bounds.getWidth()/10;
+
+ Double xCenter=bounds.getCenterX();
+ Double yCenter=bounds.getCenterY();
+
+ Double arcExtent = 60.0;
+ Double arcStart1 = 360-arcExtent/2;
+ Double arcStart2 = 180-arcExtent/2;
+
+ if(3*vMargin*Math.sin(Math.toRadians(arcExtent/2))>0.9*bounds.getHeight()/2){
+ vMargin=0.9*bounds.getHeight()/(6*Math.sin(Math.toRadians(arcExtent/2)));
+ }
+ newShape[offset]= new Ellipse2D.Double(xCenter-vMargin/2, yCenter-vMargin/2,vMargin,vMargin);
+ for(int i=1; i<4; i++){
+ newShape[i+offset]= new Arc2D.Double(xCenter-i*vMargin, yCenter-i*vMargin, 2*i*vMargin, 2*i*vMargin, arcStart1,arcExtent,Arc2D.OPEN);
+ newShape[i+3+offset]= new Arc2D.Double(xCenter-i*vMargin, yCenter-i*vMargin, 2*i*vMargin, 2*i*vMargin, arcStart2,arcExtent,Arc2D.OPEN);
+ }
+ return newShape;
+ }
+ private static Shape[] addPayloadSymbol(Shape[] baseShape){
+ Shape[] newShape = new Shape[baseShape.length+1];
+ int offset=baseShape.length;
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+ Double vMargin=bounds.getHeight()/10;
+ Double hMargin=bounds.getWidth()/10;
+
+
+ newShape[offset]= new Ellipse2D.Double(bounds.getX()+hMargin, bounds.getY()+vMargin,bounds.getWidth()-2*hMargin,bounds.getHeight()-2*vMargin);
+
+ return newShape;
+ }
+ private static Shape[] addRecoveryHardwareSymbol(Shape[] baseShape){
+ Shape[] newShape = new Shape[baseShape.length+3];
+ int offset=baseShape.length;
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+ Double vMargin=bounds.getHeight()/8;
+ Double hMargin=bounds.getWidth()/8;
+
+
+ newShape[offset]= new RoundRectangle2D.Double(bounds.getX()+hMargin, bounds.getY()+vMargin,bounds.getWidth()-2*hMargin,bounds.getHeight()-2*vMargin, 15, 5);
+ newShape[offset+1]= new RoundRectangle2D.Double(bounds.getX()+hMargin+vMargin, bounds.getY()+2*vMargin,bounds.getWidth()-2*hMargin-2*vMargin,bounds.getHeight()-4*vMargin, 15, 5);
+ newShape[offset+2]= new Rectangle2D.Double(bounds.getCenterX()-1.5*hMargin, bounds.getCenterY()+1.5*vMargin, 3*hMargin, 2*vMargin);
+ return newShape;
+ }
+
+ private static Shape[] addDeploymentChargeSymbol(Shape[] baseShape){
+ int rays=15;
+ Shape[] newShape = new Shape[baseShape.length+2];
+ int offset=baseShape.length;
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+
+ Double vMargin=bounds.getWidth()/10;
+ Double xCenter=bounds.getCenterX();
+ Double yCenter=bounds.getCenterY();
+ Random rand = new Random();
+
+ newShape[offset]= new Arc2D.Double(xCenter-2*vMargin, yCenter-2*vMargin,4*vMargin,4*vMargin, 55.0, 180.0, Arc2D.CHORD);
+
+ Path2D.Double explosion = new Path2D.Double();
+ newShape[offset+1]=explosion;
+
+ for(int i=1; i0.75*bounds.getWidth())
+ chuteDiameter=0.75*bounds.getWidth();
+
+ newShape[offset]=new Arc2D.Double(bounds.getCenterX()-chuteDiameter/2, bounds.getCenterY()-chuteDiameter/4,
+ chuteDiameter,chuteDiameter,180.0,180.0,Arc2D.PIE);
+ Path2D.Double shrouds = new Path2D.Double();
+ shrouds.moveTo(bounds.getCenterX()-chuteDiameter/2, bounds.getCenterY()+chuteDiameter/4);
+ shrouds.lineTo(bounds.getCenterX(), bounds.getCenterY()-3*chuteDiameter/4);
+ shrouds.lineTo(bounds.getCenterX()+chuteDiameter/2, bounds.getCenterY()+chuteDiameter/4);
+
+ shrouds.moveTo(bounds.getCenterX()-chuteDiameter/4, bounds.getCenterY()+chuteDiameter/4);
+ shrouds.lineTo(bounds.getCenterX(), bounds.getCenterY()-3*chuteDiameter/4);
+ shrouds.lineTo(bounds.getCenterX()+chuteDiameter/4, bounds.getCenterY()+chuteDiameter/4);
+
+ shrouds.moveTo(bounds.getCenterX(), bounds.getCenterY()+chuteDiameter/4);
+ shrouds.lineTo(bounds.getCenterX(), bounds.getCenterY()-3*chuteDiameter/4);
+
+ newShape[offset+1]=shrouds;
+ return newShape;
+ }
+}
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java
new file mode 100644
index 000000000..79ffdb89c
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java
@@ -0,0 +1,71 @@
+package net.sf.openrocket.gui.rocketfigure;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Transformation;
+
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+public class ShockCordShapes extends RocketComponentShapes {
+
+ public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ double length = tube.getLength();
+ double radius = tube.getRadius();
+ double arc = Math.min(length, 2*radius) * 0.7;
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S,
+ length*S,2*radius*S,arc*S,arc*S);
+ }
+ return addSymbol(s);
+ }
+
+
+ public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ double or = tube.getRadius();
+
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S);
+ }
+ return s;
+ }
+
+ private static Shape[] addSymbol(Shape[] baseShape){
+ int offset=baseShape.length;
+ Shape[] newShape = new Shape[baseShape.length+1];
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+
+ Double left=bounds.getX()+bounds.getWidth()/4;
+ Double cordWidth=bounds.getWidth()/2;
+ Double top=bounds.getCenterY();
+ Double flutterHeight=bounds.getHeight()/4;
+ Double flutterWidth=cordWidth/4;
+
+ Path2D.Double streamer= new Path2D.Double();
+ streamer.moveTo(left, bounds.getCenterY());
+
+ for(int i=0; i<4; i++){
+ streamer.curveTo(left+(4*i+1)*flutterWidth/4, top+flutterHeight, left+(4*i+1)*flutterWidth/4, top+flutterHeight, left+(4*i+2)*flutterWidth/4, top);
+ streamer.curveTo(left+(4*i+3)*flutterWidth/4, top-flutterHeight, left+(4*i+3)*flutterWidth/4, top-flutterHeight, left+(4*i+4)*flutterWidth/4, top);
+ }
+
+ newShape[offset]=streamer;
+ return newShape;
+ }
+}
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java
new file mode 100644
index 000000000..7a5f41bd9
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java
@@ -0,0 +1,79 @@
+package net.sf.openrocket.gui.rocketfigure;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Transformation;
+
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+public class StreamerShapes extends RocketComponentShapes {
+
+ public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ double length = tube.getLength();
+ double radius = tube.getRadius();
+ double arc = Math.min(length, 2*radius) * 0.7;
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S,
+ length*S,2*radius*S,arc*S,arc*S);
+ }
+ return addSymbol(s);
+ }
+
+
+ public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ Transformation transformation) {
+ net.sf.openrocket.rocketcomponent.MassObject tube = (net.sf.openrocket.rocketcomponent.MassObject)component;
+
+ double or = tube.getRadius();
+
+ Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
+
+ Shape[] s = new Shape[start.length];
+ for (int i=0; i < start.length; i++) {
+ s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S);
+ }
+ return s;
+ }
+
+ private static Shape[] addSymbol(Shape[] baseShape){
+ int offset=baseShape.length;
+ Shape[] newShape = new Shape[baseShape.length+1];
+ System.arraycopy(baseShape, 0, newShape, 0, baseShape.length);
+
+ Rectangle2D bounds = baseShape[0].getBounds2D();
+
+ Double left=bounds.getX()+bounds.getWidth()/4;
+ Double streamerWidth=bounds.getWidth()/2;
+ Double streamerHeight=bounds.getHeight()/2;
+ Double top=bounds.getCenterY()+streamerHeight/2;
+ Double bottom=bounds.getCenterY()-streamerHeight/2;
+ Double flutterHeight=bounds.getHeight()/16;
+ Double flutterWidth=streamerWidth/4;
+
+ Path2D.Double streamer= new Path2D.Double();
+ streamer.moveTo(left, bottom); //bottom left
+ streamer.lineTo(left, top); //upper left
+ for(int i=0; i<4; i++){
+ streamer.curveTo(left+(4*i+1)*flutterWidth/4, top+flutterHeight, left+(4*i+1)*flutterWidth/4, top+flutterHeight, left+(4*i+2)*flutterWidth/4, top);
+ streamer.curveTo(left+(4*i+3)*flutterWidth/4, top-flutterHeight, left+(4*i+3)*flutterWidth/4, top-flutterHeight, left+(4*i+4)*flutterWidth/4, top);
+ }
+ streamer.lineTo(left+streamerWidth, bottom);
+ streamer.moveTo(left, bottom); //bottom left
+ for(int i=0; i<4; i++){
+ streamer.curveTo(left+(4*i+1)*flutterWidth/4, bottom+flutterHeight, left+(4*i+1)*flutterWidth/4, bottom+flutterHeight, left+(4*i+2)*flutterWidth/4, bottom);
+ streamer.curveTo(left+(4*i+3)*flutterWidth/4, bottom-flutterHeight, left+(4*i+3)*flutterWidth/4, bottom-flutterHeight, left+(4*i+4)*flutterWidth/4, bottom);
+ }
+
+ newShape[offset]=streamer;
+ return newShape;
+ }
+}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
index c458b8e66..d011ea5ec 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
@@ -699,15 +699,17 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
}
// Start calculation process
- extraText.setCalculatingData(true);
+ if(((SwingPreferences) Application.getPreferences()).computeFlightInBackground()){
+ extraText.setCalculatingData(true);
+
+ Rocket duplicate = (Rocket) configuration.getRocket().copy();
+ Simulation simulation = ((SwingPreferences) Application.getPreferences()).getBackgroundSimulation(duplicate);
+ simulation.getOptions().setMotorConfigurationID(
+ configuration.getFlightConfigurationID());
- Rocket duplicate = (Rocket) configuration.getRocket().copy();
- Simulation simulation = ((SwingPreferences) Application.getPreferences()).getBackgroundSimulation(duplicate);
- simulation.getOptions().setMotorConfigurationID(
- configuration.getFlightConfigurationID());
-
- backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation);
- backgroundSimulationExecutor.execute(backgroundSimulationWorker);
+ backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation);
+ backgroundSimulationExecutor.execute(backgroundSimulationWorker);
+ }
}
/**
diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java
index 402dab84c..48f1ccf0e 100644
--- a/swing/src/net/sf/openrocket/gui/util/Icons.java
+++ b/swing/src/net/sf/openrocket/gui/util/Icons.java
@@ -31,6 +31,7 @@ public class Icons {
static {
HashMap map = new HashMap();
map.put(Simulation.Status.NOT_SIMULATED, loadImageIcon("pix/spheres/gray-16x16.png", "Not simulated"));
+ map.put(Simulation.Status.CANT_RUN, loadImageIcon("pix/spheres/yellow-16x16.png", "Can't run, no motors assigned."));
map.put(Simulation.Status.UPTODATE, loadImageIcon("pix/spheres/green-16x16.png", "Up to date"));
map.put(Simulation.Status.LOADED, loadImageIcon("pix/spheres/yellow-16x16.png", "Loaded from file"));
map.put(Simulation.Status.OUTDATED, loadImageIcon("pix/spheres/red-16x16.png", "Out-of-date"));
@@ -85,6 +86,9 @@ public class Icons {
public static final Icon NOT_FAVORITE = loadImageIcon("pix/icons/star_silver.png", "Not favorite");
public static final Icon FAVORITE = loadImageIcon("pix/icons/star_gold.png", "Favorite");
+ public static final Icon CG_OVERRIDE = loadImageIcon("pix/icons/cg-override.png", "CG Override");
+ public static final Icon MASS_OVERRIDE = loadImageIcon("pix/icons/mass-override.png", "Mass Override");
+
static {
log.debug("Icons loaded");
diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
index 50ef74b16..499620c9b 100644
--- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
+++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
@@ -46,6 +46,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
list.add(new Locale(lang));
}
list.add(new Locale("zh","CN"));
+ list.add(new Locale("uk","UA"));
SUPPORTED_LOCALES = Collections.unmodifiableList(list);
}
@@ -417,7 +418,11 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public boolean computeFlightInBackground() {
return PREFNODE.getBoolean("backgroundFlight", true);
}
-
+
+ public void setComputeFlightInBackground(boolean b) {
+ PREFNODE.putBoolean("backgroundFlight", b);
+ }
+
public Simulation getBackgroundSimulation(Rocket rocket) {
Simulation s = new Simulation(rocket);
SimulationOptions cond = s.getOptions();
diff --git a/swing/src/net/sf/openrocket/startup/Startup.java b/swing/src/net/sf/openrocket/startup/OpenRocket.java
similarity index 98%
rename from swing/src/net/sf/openrocket/startup/Startup.java
rename to swing/src/net/sf/openrocket/startup/OpenRocket.java
index 2bc9754fa..9fb917bbd 100644
--- a/swing/src/net/sf/openrocket/startup/Startup.java
+++ b/swing/src/net/sf/openrocket/startup/OpenRocket.java
@@ -23,7 +23,7 @@ import net.sf.openrocket.startup.jij.PluginClasspathProvider;
*
* @author Sampo Niskanen
*/
-public class Startup {
+public class OpenRocket {
private static final String STARTUP_CLASS = "net.sf.openrocket.startup.SwingStartup";