diff --git a/core/resources-src/pix/componenticons/stage-large.xcf.gz b/core/resources-src/pix/componenticons/stage-large.xcf.gz index 21e9bea86..b200ce7f3 100644 Binary files a/core/resources-src/pix/componenticons/stage-large.xcf.gz and b/core/resources-src/pix/componenticons/stage-large.xcf.gz differ diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 86844e7a9..1b94f637b 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1149,6 +1149,8 @@ NoseConeCfg.tab.General = General NoseConeCfg.tab.ttip.General = General properties NoseConeCfg.tab.Shoulder = Shoulder NoseConeCfg.tab.ttip.Shoulder = Shoulder properties +NoseConeCfg.checkbox.Flip = Flip to tail cone +NoseConeCfg.checkbox.Flip.ttip = Flips the nose cone direction to a tail cone. ! ParachuteConfig Parachute.Parachute = Parachute @@ -1363,7 +1365,7 @@ TCMotorSelPan.lbl.Datapoints = Data points: TCMotorSelPan.lbl.Digest = Digest: TCMotorSelPan.title.Thrustcurve = Thrust curve: TCMotorSelPan.title.Thrust = Thrust -TCMotorSelPan.delayBox.None = None +TCMotorSelPan.delayBox.None = Plugged (none) TCMotorSelPan.noDescription = No description available. TCMotorSelPan.btn.checkAll = Select All TCMotorSelPan.btn.checkNone = Clear All diff --git a/core/resources/l10n/messages_ja.properties b/core/resources/l10n/messages_ja.properties index 862f12ab1..b7feb73b0 100644 --- a/core/resources/l10n/messages_ja.properties +++ b/core/resources/l10n/messages_ja.properties @@ -1046,7 +1046,7 @@ TCMotorSelPan.lbl.Datapoints = \u30C7\u30FC\u30BF\u70B9\uFF1A TCMotorSelPan.lbl.Digest = \u30C0\u30A4\u30B8\u30A7\u30B9\u30C8\uFF1A TCMotorSelPan.title.Thrustcurve = \u63A8\u529B\u5C65\u6B74\uFF1A TCMotorSelPan.title.Thrust = \u63A8\u529B -TCMotorSelPan.delayBox.None = None +TCMotorSelPan.delayBox.None = Plugged (none) TCMotorSelPan.noDescription = No description available. diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties index 842b601b4..e83f1bd0a 100644 --- a/core/resources/l10n/messages_uk_UA.properties +++ b/core/resources/l10n/messages_uk_UA.properties @@ -1119,7 +1119,7 @@ TCMotorSelPan.lbl.Datapoints = Data points: TCMotorSelPan.lbl.Digest = Digest: TCMotorSelPan.title.Thrustcurve = Thrust curve: TCMotorSelPan.title.Thrust = Thrust -TCMotorSelPan.delayBox.None = None +TCMotorSelPan.delayBox.None = Plugged (none) TCMotorSelPan.noDescription = No description available. TCMotorSelPan.btn.checkAll = Select All TCMotorSelPan.btn.checkNone = Clear All diff --git a/core/resources/pix/componenticons/stage-large.png b/core/resources/pix/componenticons/stage-large.png index e8df43fe0..6d0c8fc32 100644 Binary files a/core/resources/pix/componenticons/stage-large.png and b/core/resources/pix/componenticons/stage-large.png differ diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java new file mode 100644 index 000000000..a94bd76e5 --- /dev/null +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java @@ -0,0 +1,102 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import java.util.List; +import java.lang.Math; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.RailButton; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Transformation; + +public class RailButtonCalc extends RocketComponentCalc { + private final static Logger log = LoggerFactory.getLogger(RailButtonCalc.class); + + // values transcribed from Gowen and Perkins, "Drag of Circular Cylinders for a Wide Range + // of Reynolds Numbers and Mach Numbers", NACA Technical Note 2960, Figure 7 + private static final List cdDomain = List.of(0.0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 1.0, 1.6, 2.0, 2.8, 100.0); + private static final List cdRange = List.of(1.2, 1.22, 1.25, 1.3, 1.4, 1.5, 1.6, 2.1, 1.5, 1.45, 1.33, 1.33); + + private final RailButton button; + + public RailButtonCalc(RocketComponent component) { + super(component); + + // need to stash the button + button = (RailButton) component; + } + + @Override + public double calculateFrictionCD(FlightConditions conditions, double componentCf, WarningSet warnings) { + // very small relative surface area, and slick + return 0.0; + } + + @Override + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, + AerodynamicForces forces, WarningSet warnings) { + // Nothing to be done + } + + @Override + public double calculatePressureCD(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + + // grab relevant button params + final int instanceCount = button.getInstanceCount(); + final Coordinate[] instanceOffsets = button.getInstanceOffsets(); + + // compute button reference area + final double buttonHt = button.getTotalHeight(); + final double outerArea = buttonHt * button.getOuterDiameter(); + final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight(); + final double refArea = outerArea - notchArea; + + // accumulate Cd contribution from each rail button + double CDmul = 0.0; + for (int i = 0; i < button.getInstanceCount(); i++) { + + // compute boundary layer height at button location. I can't find a good reference for the + // formula, e.g. https://aerospaceengineeringblog.com/boundary-layers/ simply says it's the + // "scientific consensus". + double x = (button.toAbsolute(instanceOffsets[i]))[0].x; // location of button + double rex = calculateReynoldsNumber(x, conditions); // Reynolds number of button location + double del = 0.37 * x / Math.pow(rex, 0.2); // Boundary layer thickness + + // compute mean airspeed over button + // this assumes airspeed changes linearly through boundary layer + // and that all parts of the railbutton contribute equally to Cd, + // neither of which is true but both are plenty close enough for our purposes + + double mach; + if (buttonHt > del) { + // Case 1: button extends beyond boundary layer + // Mean velocity is 1/2 rocket velocity up to limit of boundary layer, + // full velocity after that + mach = (buttonHt - 0.5*del) * conditions.getMach()/buttonHt; + } else { + // Case 2: button is entirely within boundary layer + mach = MathUtil.map(buttonHt/2.0, 0, del, 0, conditions.getMach()); + } + + // look up Cd as function of speed. It's pretty constant as a function of Reynolds + // number when slow, so we can just use a function of Mach number + double cd = MathUtil.interpolate(cdDomain, cdRange, mach); + + // Since later drag force calculations don't consider boundary layer, compute "effective Cd" + // based on rocket velocity + cd = cd * MathUtil.pow2(mach)/MathUtil.pow2(conditions.getMach()); + + // add to CDmul + CDmul += cd; + } + + return CDmul * stagnationCD * refArea / conditions.getRefArea(); + } +} diff --git a/core/src/net/sf/openrocket/database/motor/ThrustCurveMotorSet.java b/core/src/net/sf/openrocket/database/motor/ThrustCurveMotorSet.java index b48ed3344..edadd5f8f 100644 --- a/core/src/net/sf/openrocket/database/motor/ThrustCurveMotorSet.java +++ b/core/src/net/sf/openrocket/database/motor/ThrustCurveMotorSet.java @@ -220,6 +220,9 @@ public class ThrustCurveMotorSet implements Comparable { (type != m.getMotorType())) { return false; } + + if (!designation.equalsIgnoreCase(m.getDesignation())) + return false; if (!commonName.equalsIgnoreCase(m.getCommonName())) return false; diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index 9a75dbbf8..95a93af50 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -384,15 +384,19 @@ public class Simulation implements ChangeSource, Cloneable { simulatedData = simulator.simulate(simulationConditions); t2 = System.currentTimeMillis(); log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms"); - - // Set simulated info after simulation, will not be set in case of exception + + } catch (SimulationException e) { + simulatedData = e.getFlightData(); + throw e; + } finally { + // Set simulated info after simulation simulatedConditions = options.clone(); simulatedConfigurationDescription = descriptor.format( this.rocket, getId()); simulatedRocketID = rocket.getFunctionalModID(); status = Status.UPTODATE; fireChangeEvent(); - } finally { + mutex.unlock("simulate"); } } diff --git a/core/src/net/sf/openrocket/file/motor/ZipFileMotorLoader.java b/core/src/net/sf/openrocket/file/motor/ZipFileMotorLoader.java index 176da2dc6..5bed72943 100644 --- a/core/src/net/sf/openrocket/file/motor/ZipFileMotorLoader.java +++ b/core/src/net/sf/openrocket/file/motor/ZipFileMotorLoader.java @@ -44,7 +44,7 @@ public class ZipFileMotorLoader implements MotorLoader { @Override - public List load(InputStream stream, String filename) throws IOException { + public List load(InputStream stream, String filename) throws IOException, IllegalArgumentException { List motors = new ArrayList<>(); ZipInputStream is = new ZipInputStream(stream); diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java index b0aef58c0..8de389fa8 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java @@ -10,9 +10,11 @@ import net.sf.openrocket.util.Reflection; //// BooleanSetter - set a boolean value class BooleanSetter implements Setter { private final Reflection.Method setMethod; + private Object[] extraParameters = null; - public BooleanSetter(Reflection.Method set) { + public BooleanSetter(Reflection.Method set, Object... parameters) { setMethod = set; + this.extraParameters = parameters; } @Override @@ -20,12 +22,23 @@ class BooleanSetter implements Setter { WarningSet warnings) { s = s.trim(); + final boolean setValue; if (s.equalsIgnoreCase("true")) { - setMethod.invoke(c, true); + setValue = true; } else if (s.equalsIgnoreCase("false")) { - setMethod.invoke(c, false); + setValue = false; } else { warnings.add(Warning.FILE_INVALID_PARAMETER); + return; + } + + if (extraParameters != null) { + Object[] parameters = new Object[extraParameters.length + 1]; + parameters[0] = setValue; + System.arraycopy(extraParameters, 0, parameters, 1, extraParameters.length); + setMethod.invoke(c, parameters); + } else { + setMethod.invoke(c, setValue); } } } \ No newline at end of file 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 2366345ef..00b6c1467 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java @@ -248,7 +248,9 @@ class DocumentConfig { Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class))); setters.put("Transition:aftshouldercapped", new BooleanSetter( Reflection.findMethod(Transition.class, "setAftShoulderCapped", boolean.class))); - + + setters.put("NoseCone:isflipped", new BooleanSetter( + Reflection.findMethod(NoseCone.class, "setFlipped", boolean.class, boolean.class), false)); // NoseCone - disable disallowed elements setters.put("NoseCone:foreradius", null); setters.put("NoseCone:foreshoulderradius", null); diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java index 9733254f8..e5c24e571 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java @@ -1,7 +1,10 @@ package net.sf.openrocket.file.openrocket.savers; +import net.sf.openrocket.rocketcomponent.NoseCone; + import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class NoseConeSaver extends TransitionSaver { @@ -20,8 +23,23 @@ public class NoseConeSaver extends TransitionSaver { @Override protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { + NoseCone noseCone = (NoseCone) c; super.addParams(c, elements); - - // Transition handles nose cone saving as well + + if (noseCone.isBaseRadiusAutomatic()) + elements.add("auto " + noseCone.getBaseRadiusNoAutomatic() + ""); + else + elements.add("" + noseCone.getBaseRadius() + ""); + + elements.add("" + noseCone.getShoulderRadius() + + ""); + elements.add("" + noseCone.getShoulderLength() + + ""); + elements.add("" + noseCone.getShoulderThickness() + + ""); + elements.add("" + noseCone.isShoulderCapped() + + ""); + + elements.add("" + noseCone.isFlipped() + ""); } } diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java index 054506eae..ca02534d7 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java @@ -30,8 +30,6 @@ public class TransitionSaver extends SymmetricComponentSaver { protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { super.addParams(c, elements); net.sf.openrocket.rocketcomponent.Transition trans = (net.sf.openrocket.rocketcomponent.Transition) c; - boolean nosecone = (trans instanceof NoseCone); - Transition.Shape shape = trans.getType(); elements.add("" + shape.name().toLowerCase(Locale.ENGLISH) + ""); @@ -41,14 +39,16 @@ public class TransitionSaver extends SymmetricComponentSaver { if (shape.usesParameter()) { elements.add("" + trans.getShapeParameter() + ""); } - - - if (!nosecone) { - if (trans.isForeRadiusAutomatic()) - elements.add("auto " + trans.getForeRadiusNoAutomatic() + ""); - else - elements.add("" + trans.getForeRadius() + ""); + + // Nose cones need other parameter saving, due to the isFlipped() parameter + if (trans instanceof NoseCone) { + return; } + + if (trans.isForeRadiusAutomatic()) + elements.add("auto " + trans.getForeRadiusNoAutomatic() + ""); + else + elements.add("" + trans.getForeRadius() + ""); if (trans.isAftRadiusAutomatic()) elements.add("auto " + trans.getAftRadiusNoAutomatic() + ""); @@ -56,16 +56,14 @@ public class TransitionSaver extends SymmetricComponentSaver { elements.add("" + trans.getAftRadius() + ""); - if (!nosecone) { - elements.add("" + trans.getForeShoulderRadius() - + ""); - elements.add("" + trans.getForeShoulderLength() - + ""); - elements.add("" + trans.getForeShoulderThickness() - + ""); - elements.add("" + trans.isForeShoulderCapped() - + ""); - } + elements.add("" + trans.getForeShoulderRadius() + + ""); + elements.add("" + trans.getForeShoulderLength() + + ""); + elements.add("" + trans.getForeShoulderThickness() + + ""); + elements.add("" + trans.isForeShoulderCapped() + + ""); elements.add("" + trans.getAftShoulderRadius() + ""); diff --git a/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java b/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java index 7baa9b56f..d2be13493 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java @@ -65,7 +65,11 @@ public class PodSetDTO extends BasePartDTO implements AttachableParts { } else if (child instanceof BodyTube) { addAttachedPart(new BodyTubeDTO((BodyTube) child)); } else if (child instanceof NoseCone) { - addAttachedPart(new NoseConeDTO((NoseCone) child)); + if (((NoseCone) child).isFlipped()) { + addAttachedPart(new TransitionDTO((NoseCone) child)); + } else { + addAttachedPart(new NoseConeDTO((NoseCone) child)); + } } else if (child instanceof Transition) { addAttachedPart(new TransitionDTO((Transition) child)); } diff --git a/core/src/net/sf/openrocket/file/rocksim/export/StageDTO.java b/core/src/net/sf/openrocket/file/rocksim/export/StageDTO.java index 7cbe4a7cf..f0c167966 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/StageDTO.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/StageDTO.java @@ -93,8 +93,12 @@ public class StageDTO { externalPart.add(theExternalPartDTO); } - private NoseConeDTO toNoseConeDTO(NoseCone nc) { - return new NoseConeDTO(nc); + private AbstractTransitionDTO toNoseConeDTO(NoseCone nc) { + if (nc.isFlipped()) { + return new TransitionDTO(nc); + } else { + return new NoseConeDTO(nc); + } } private BodyTubeDTO toBodyTubeDTO(BodyTube bt) { diff --git a/core/src/net/sf/openrocket/optimization/services/DefaultSimulationModifierService.java b/core/src/net/sf/openrocket/optimization/services/DefaultSimulationModifierService.java index 47a122811..79f6e3ef8 100644 --- a/core/src/net/sf/openrocket/optimization/services/DefaultSimulationModifierService.java +++ b/core/src/net/sf/openrocket/optimization/services/DefaultSimulationModifierService.java @@ -56,7 +56,7 @@ public class DefaultSimulationModifierService implements SimulationModifierServi */ addModifier("optimization.modifier.nosecone.length", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Length"); - addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius", "isAftRadiusAutomatic"); + addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "BaseRadius", "isBaseRadiusAutomatic"); addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness", "isFilled"); addModifier("optimization.modifier.transition.length", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Length"); diff --git a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java index ed436981d..1bbaefc1e 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java +++ b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -13,6 +13,9 @@ import java.util.EventObject; /** * Rocket nose cones of various types. Implemented as a transition with the * fore radius == 0. + *

+ * The normal nose cone can be converted to a tail cone by setting the {@link #isFlipped} parameter. + * This will flip all the aft side dimensions with the fore side dimensions. * * @author Sampo Niskanen */ @@ -21,6 +24,7 @@ public class NoseCone extends Transition implements InsideColorComponent { private static final Translator trans = Application.getTranslator(); private InsideColorComponentHandler insideColorComponentHandler = new InsideColorComponentHandler(this); + private boolean isFlipped; // If true, the nose cone is converted to a tail cone /********* Constructors **********/ public NoseCone() { @@ -29,16 +33,12 @@ public class NoseCone extends Transition implements InsideColorComponent { public NoseCone(Transition.Shape type, double length, double radius) { super(); + this.isFlipped = false; super.setType(type); - super.setForeRadiusAutomatic(false); - super.setForeRadius(0); - super.setForeShoulderLength(0); - super.setForeShoulderRadius(0.9 * radius); - super.setForeShoulderThickness(0); - super.setForeShoulderCapped(filled); super.setThickness(0.002); super.setLength(length); super.setClipped(false); + resetForeRadius(); super.setAftRadiusAutomatic(false); super.setAftRadius(radius); @@ -46,86 +46,220 @@ public class NoseCone extends Transition implements InsideColorComponent { super.displayOrder_side = 1; // Order for displaying the component in the 2D side view super.displayOrder_back = 0; // Order for displaying the component in the 2D back view } - - - /********** Get/set methods for component parameters **********/ - - @Override - public double getForeRadius() { - return 0; - } - - @Override - public void setForeRadius(double r) { - // No-op - } - - @Override - public boolean isForeRadiusAutomatic() { - return false; - } - - @Override - public void setForeRadiusAutomatic(boolean b) { - // No-op + + /********** Nose cone dimensions **********/ + + /** + * Returns the base radius of the nose cone (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #getAftRadius()} because it works for both normal and flipped nose cones. + */ + public double getBaseRadius() { + return isFlipped ? getForeRadius() : getAftRadius(); } - @Override - public boolean usesPreviousCompAutomatic() { - return false; + /** + * Returns the raw base radius of the nose cone (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #getAftRadiusNoAutomatic()} because it works for both normal and flipped nose cones. + */ + public double getBaseRadiusNoAutomatic() { + return isFlipped ? getForeRadiusNoAutomatic() : getAftRadiusNoAutomatic(); } - @Override - public double getForeShoulderLength() { - return 0; + /** + * Sets the base radius of the nose cone (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftRadius(double)} because it works for both normal and flipped nose cones. + */ + public void setBaseRadius(double radius) { + if (isFlipped) { + setForeRadius(radius); + } else { + setAftRadius(radius); + } } - - @Override - public double getForeShoulderRadius() { - return 0; + + /** + * Returns whether the base radius of the nose cone takes it settings from the previous/next component + * (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #isAftRadiusAutomatic()} because it works for both normal and flipped nose cones. + */ + public boolean isBaseRadiusAutomatic() { + return isFlipped ? isForeRadiusAutomatic() : isAftRadiusAutomatic(); } - - @Override - public double getForeShoulderThickness() { - return 0; + + /** + * Sets whether the base radius of the nose cone takes it settings from the previous/next component + * (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftRadiusAutomatic(boolean)} because it works for both normal and flipped nose cones. + */ + public void setBaseRadiusAutomatic(boolean auto) { + if (isFlipped) { + setForeRadiusAutomatic(auto); + } else { + setAftRadiusAutomatic(auto); + } + } - - @Override - public boolean isForeShoulderCapped() { - return false; + + /** + * Returns the shoulder length, regardless of how the nose cone is flipped (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #getAftShoulderLength()} because it works for both normal and flipped nose cones. + */ + public double getShoulderLength() { + return isFlipped ? getForeShoulderLength() : getAftShoulderLength(); } - - @Override - public void setForeShoulderCapped(boolean capped) { - // No-op + + /** + * Sets the shoulder length (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftShoulderLength(double)} because it works for both normal and flipped nose cones. + */ + public void setShoulderLength(double length) { + if (isFlipped) { + setForeShoulderLength(length); + } else { + setAftShoulderLength(length); + } } - - @Override - public void setForeShoulderLength(double foreShoulderLength) { - // No-op + + /** + * Returns the shoulder radius (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #getAftShoulderRadius()} because it works for both normal and flipped nose cones. + */ + public double getShoulderRadius() { + return isFlipped ? getForeShoulderRadius() : getAftShoulderRadius(); } - - @Override - public void setForeShoulderRadius(double foreShoulderRadius) { - // No-op + + /** + * Sets the shoulder radius (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftShoulderRadius(double)} because it works for both normal and flipped nose cones. + */ + public void setShoulderRadius(double radius) { + if (isFlipped) { + setForeShoulderRadius(radius); + } else { + setAftShoulderRadius(radius); + } } - - @Override - public void setForeShoulderThickness(double foreShoulderThickness) { - // No-op + + /** + * Returns the shoulder thickness (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #getAftShoulderThickness()} because it works for both normal and flipped nose cones. + */ + public double getShoulderThickness() { + return isFlipped ? getForeShoulderThickness() : getAftShoulderThickness(); } - + + /** + * Sets the shoulder thickness (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftShoulderRadius(double)} because it works for both normal and flipped nose cones. + */ + public void setShoulderThickness(double thickness) { + if (isFlipped) { + setForeShoulderThickness(thickness); + } else { + setAftShoulderThickness(thickness); + } + } + + /** + * Returns the shoulder cap (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #isAftShoulderCapped()} because it works for both normal and flipped nose cones. + */ + public boolean isShoulderCapped() { + return isFlipped ? isForeShoulderCapped() : isAftShoulderCapped(); + } + + /** + * Sets the shoulder cap (independent of whether the nose cone is flipped or not). + * This method should be used over {@link #setAftShoulderCapped(boolean)} because it works for both normal and flipped nose cones. + */ + public void setShoulderCapped(boolean capped) { + if (isFlipped) { + setForeShoulderCapped(capped); + } else { + setAftShoulderCapped(capped); + } + } + + /********** Other **********/ + + /** + * Return true if the nose cone is flipped, i.e. converted to a tail cone, false if it is a regular nose cone. + */ + public boolean isFlipped() { + return isFlipped; + } + + /** + * Set the nose cone to be flipped, i.e. converted to a tail cone, or set it to be a regular nose cone. + * @param flipped if true, the nose cone is converted to a tail cone, if false it is a regular nose cone. + * @param sanityCheck whether to check if the auto radius parameter can be used for the new nose cone orientation + */ + public void setFlipped(boolean flipped, boolean sanityCheck) { + if (isFlipped == flipped) { + return; + } + + setBypassChangeEvent(true); + if (flipped) { + setForeRadius(getAftRadiusNoAutomatic()); + setForeRadiusAutomatic(isAftRadiusAutomatic(), sanityCheck); + setForeShoulderLength(getAftShoulderLength()); + setForeShoulderRadius(getAftShoulderRadius()); + setForeShoulderThickness(getAftShoulderThickness()); + setForeShoulderCapped(isAftShoulderCapped()); + + resetAftRadius(); + } else { + setAftRadius(getForeRadiusNoAutomatic()); + setAftRadiusAutomatic(isForeRadiusAutomatic(), sanityCheck); + setAftShoulderLength(getForeShoulderLength()); + setAftShoulderRadius(getForeShoulderRadius()); + setAftShoulderThickness(getForeShoulderThickness()); + setAftShoulderCapped(isForeShoulderCapped()); + + resetForeRadius(); + } + setBypassChangeEvent(false); + + isFlipped = flipped; + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + } + + /** + * Set the nose cone to be flipped, i.e. converted to a tail cone, or set it to be a regular nose cone. + * @param flipped if true, the nose cone is converted to a tail cone, if false it is a regular nose cone. + */ + public void setFlipped(boolean flipped) { + setFlipped(flipped, true); + } + + private void resetForeRadius() { + setForeRadius(0); + setForeRadiusAutomatic(false); + setForeShoulderLength(0); + setForeShoulderRadius(0); + setForeShoulderThickness(0); + setForeShoulderCapped(false); + } + + private void resetAftRadius() { + setAftRadius(0); + setAftRadiusAutomatic(false); + setAftShoulderLength(0); + setAftShoulderRadius(0); + setAftShoulderThickness(0); + setAftShoulderCapped(false); + } + @Override public boolean isClipped() { return false; } - + @Override public void setClipped(boolean b) { // No-op } - - /********** RocketComponent methods **********/ @@ -136,7 +270,7 @@ public class NoseCone extends Transition implements InsideColorComponent { @Override protected void loadFromPreset(ComponentPreset preset) { - + setFlipped(false); //Many parameters are handled by the super class Transition.loadFromPreset super.loadFromPreset(preset); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java index 504de01e3..a25e616a0 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java @@ -207,12 +207,6 @@ public class RailButton extends ExternalComponent implements AnglePositionable, clearPreset(); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } - - @Override - public boolean isAerodynamic(){ - // TODO: implement aerodynamics - return false; - } @Override public double getAngleOffset(){ diff --git a/core/src/net/sf/openrocket/rocketcomponent/Transition.java b/core/src/net/sf/openrocket/rocketcomponent/Transition.java index 9fd78a378..76c56b477 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Transition.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -5,17 +5,13 @@ import static net.sf.openrocket.util.MathUtil.pow2; import static net.sf.openrocket.util.MathUtil.pow3; import java.util.Collection; -import java.util.EventObject; -import net.sf.openrocket.appearance.Appearance; -import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPreset.Type; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; -import net.sf.openrocket.util.StateChangeListener; public class Transition extends SymmetricComponent implements InsideColorComponent { @@ -27,8 +23,8 @@ public class Transition extends SymmetricComponent implements InsideColorCompone private double shapeParameter; private boolean clipped; // Not to be read - use isClipped(), which may be overridden - private double foreRadius, aftRadius; - private boolean autoForeRadius, autoAftRadius2; // Whether the start radius is automatic + protected double foreRadius, aftRadius; // Warning: avoid using these directly, use getForeRadius() and getAftRadius() instead (because the definition of the two can change for flipped nose cones) + protected boolean autoForeRadius, autoAftRadius; // Whether the start radius is automatic private double foreShoulderRadius; @@ -53,7 +49,7 @@ public class Transition extends SymmetricComponent implements InsideColorCompone this.aftRadius = DEFAULT_RADIUS; this.length = DEFAULT_RADIUS * 3; this.autoForeRadius = true; - this.autoAftRadius2 = true; + this.autoAftRadius = true; this.type = Shape.CONICAL; this.shapeParameter = 0; @@ -81,19 +77,24 @@ public class Transition extends SymmetricComponent implements InsideColorCompone @Override public double getForeRadius() { if (isForeRadiusAutomatic()) { - // Get the automatic radius from the front - double r = -1; - SymmetricComponent c = this.getPreviousSymmetricComponent(); - if (c != null) { - r = c.getFrontAutoRadius(); - } - if (r < 0) - r = DEFAULT_RADIUS; - return r; + return getAutoForeRadius(); } return foreRadius; } + /** + * Returns the automatic radius from the front, taken from the previous component. Returns the default radius if there + * is no previous component. + */ + protected double getAutoForeRadius() { + SymmetricComponent c = this.getPreviousSymmetricComponent(); + if (c != null) { + return c.getFrontAutoRadius(); + } else { + return DEFAULT_RADIUS; + } + } + /** * Return the fore radius that was manually entered, so not the value that the component received from automatic * fore radius. @@ -136,13 +137,24 @@ public class Transition extends SymmetricComponent implements InsideColorCompone return autoForeRadius; } - public void setForeRadiusAutomatic(boolean auto) { + /** + * Set the fore radius to automatic mode (takes its value from the previous symmetric component's radius). + * + * @param auto whether to set the fore radius to automatic mode + * @param sanityCheck whether to sanity check auto mode for whether there is a previous component of which you can take the radius + */ + public void setForeRadiusAutomatic(boolean auto, boolean sanityCheck) { for (RocketComponent listener : configListeners) { if (listener instanceof Transition) { ((Transition) listener).setForeRadiusAutomatic(auto); } } + // You can only set the auto fore radius if it is possible + if (sanityCheck) { + auto = auto && canUsePreviousCompAutomatic(); + } + if (autoForeRadius == auto) return; @@ -152,25 +164,34 @@ public class Transition extends SymmetricComponent implements InsideColorCompone fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } + public void setForeRadiusAutomatic(boolean auto) { + setForeRadiusAutomatic(auto, false); + } + //////// Aft radius ///////// @Override public double getAftRadius() { if (isAftRadiusAutomatic()) { - // Return the auto radius from the rear - double r = -1; - SymmetricComponent c = this.getNextSymmetricComponent(); - if (c != null) { - r = c.getRearAutoRadius(); - } - if (r < 0) - r = DEFAULT_RADIUS; - return r; + return getAutoAftRadius(); } return aftRadius; } + /** + * Returns the automatic radius from the rear, taken from the next component. Returns the default radius if there + * is no next component. + */ + protected double getAutoAftRadius() { + SymmetricComponent c = this.getNextSymmetricComponent(); + if (c != null) { + return c.getRearAutoRadius(); + } else { + return DEFAULT_RADIUS; + } + } + /** * Return the aft radius that was manually entered, so not the value that the component received from automatic * zft radius. @@ -191,10 +212,10 @@ public class Transition extends SymmetricComponent implements InsideColorCompone } } - if ((this.aftRadius == radius) && (autoAftRadius2 == false)) + if ((this.aftRadius == radius) && (autoAftRadius == false)) return; - this.autoAftRadius2 = false; + this.autoAftRadius = false; this.aftRadius = Math.max(radius, 0); if (doClamping && this.thickness > this.foreRadius && this.thickness > this.aftRadius) @@ -210,25 +231,40 @@ public class Transition extends SymmetricComponent implements InsideColorCompone @Override public boolean isAftRadiusAutomatic() { - return autoAftRadius2; + return autoAftRadius; } - public void setAftRadiusAutomatic(boolean auto) { + /** + * Set the aft radius to automatic mode (takes its value from the next symmetric component's radius). + * + * @param auto whether to set the aft radius to automatic mode + * @param sanityCheck whether to sanity check auto mode for whether there is a next component of which you can take the radius + */ + public void setAftRadiusAutomatic(boolean auto, boolean sanityCheck) { for (RocketComponent listener : configListeners) { if (listener instanceof Transition) { ((Transition) listener).setAftRadiusAutomatic(auto); } } - if (autoAftRadius2 == auto) + // You can only set the auto aft radius if it is possible + if (sanityCheck) { + auto = auto && canUseNextCompAutomatic(); + } + + if (autoAftRadius == auto) return; - autoAftRadius2 = auto; + autoAftRadius = auto; clearPreset(); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } + public void setAftRadiusAutomatic(boolean auto) { + setAftRadiusAutomatic(auto, false); + } + //// Radius automatics @@ -257,6 +293,32 @@ public class Transition extends SymmetricComponent implements InsideColorCompone return isAftRadiusAutomatic(); } + /** + * Checks whether this component can use the automatic radius of the previous symmetric component. + * @return false if there is no previous symmetric component, or if the previous component already has this component + * as its auto dimension reference + */ + public boolean canUsePreviousCompAutomatic() { + SymmetricComponent referenceComp = getPreviousSymmetricComponent(); + if (referenceComp == null) { + return false; + } + return !referenceComp.usesNextCompAutomatic(); + } + + /** + * Checks whether this component can use the automatic radius of the next symmetric component. + * @return false if there is no next symmetric component, or if the next component already has this component + * as its auto dimension reference + */ + public boolean canUseNextCompAutomatic() { + SymmetricComponent referenceComp = getNextSymmetricComponent(); + if (referenceComp == null) { + return false; + } + return !referenceComp.usesPreviousCompAutomatic(); + } + //////// Type & shape ///////// diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 682b21b94..513190b8b 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -58,12 +58,14 @@ public class BasicEventSimulationEngine implements SimulationEngine { // this is just a list of simulation branches to Deque toSimulate = new ArrayDeque(); + + FlightData flightData; @Override public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException { // Set up flight data - FlightData flightData = new FlightData(); + flightData = new FlightData(); // Set up rocket configuration this.fcid = simulationConditions.getFlightConfigurationID(); @@ -268,6 +270,11 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Add FlightEvent for Abort. currentStatus.getFlightData().addEvent(new FlightEvent(FlightEvent.Type.EXCEPTION, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), e.getLocalizedMessage())); + + flightData.addBranch(currentStatus.getFlightData()); + flightData.getWarningSet().addAll(currentStatus.getWarnings()); + + e.setFlightData(flightData); throw e; } diff --git a/core/src/net/sf/openrocket/simulation/exception/SimulationException.java b/core/src/net/sf/openrocket/simulation/exception/SimulationException.java index f180f892c..e0e8ee8ec 100644 --- a/core/src/net/sf/openrocket/simulation/exception/SimulationException.java +++ b/core/src/net/sf/openrocket/simulation/exception/SimulationException.java @@ -1,7 +1,11 @@ package net.sf.openrocket.simulation.exception; +import net.sf.openrocket.simulation.FlightData; + public class SimulationException extends Exception { + private FlightData flightData = null; + public SimulationException() { } @@ -18,4 +22,11 @@ public class SimulationException extends Exception { super(message, cause); } + public void setFlightData(FlightData f) { + flightData = f; + } + + public FlightData getFlightData() { + return flightData; + } } diff --git a/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java new file mode 100644 index 000000000..513b0c922 --- /dev/null +++ b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java @@ -0,0 +1,101 @@ +package net.sf.openrocket.aerodynamics; + +import static org.junit.Assert.assertEquals; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; + +import net.sf.openrocket.ServicesForTesting; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.barrowman.RailButtonCalc; +import net.sf.openrocket.plugin.PluginModule; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.RailButton; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.TestRockets; +import net.sf.openrocket.util.Transformation; + +public class RailButtonCalcTest { + protected final double EPSILON = 0.0001; + + private static Injector injector; + @BeforeClass + public static void setup() { + Module applicationModule = new ServicesForTesting(); + Module pluginModule = new PluginModule(); + + injector = Guice.createInjector( applicationModule, pluginModule); + Application.setInjector(injector); + } + + @Test + public void testRailButtons() { + + Rocket rocket = TestRockets.makeEstesAlphaIII(); + FlightConfiguration config = rocket.getSelectedConfiguration(); + + // Get the body tube... + BodyTube tube = (BodyTube)rocket.getChild(0).getChild(1); + + // Replace the launch lug with a (single) railbutton + LaunchLug lug = (LaunchLug)tube.getChild(1); + rocket.removeChild(lug); + + RailButton button = new RailButton(); + tube.addChild(button); + + // Button parameters from Binder Design standard 1010 + button.setOuterDiameter(0.011); + button.setInnerDiameter(0.006); + + button.setBaseHeight(0.002); + button.setFlangeHeight(0.002); + button.setTotalHeight(0.008); + + button.setAxialMethod(AxialMethod.ABSOLUTE); + button.setAxialOffset(1.0); + + // Set up flight conditions + FlightConditions conditions = new FlightConditions(config); + conditions.setMach(1.0); + + BarrowmanCalculator barrowmanObj = new BarrowmanCalculator(); + RailButtonCalc calcObj = new RailButtonCalc(button); + + // Calculate effective CD for rail button + // Boundary layer height + double rex = calcObj.calculateReynoldsNumber(1.0, conditions); // Reynolds number of button location + double del = 0.37 * 1.0 / Math.pow(rex, 0.2); // Boundary layer height + + // Interpolate velocity at midpoint of railbutton + double mach = MathUtil.map(0.008/2.0, 0, del, 0, 1.0); + + // Interpolate to get CD + double cd = MathUtil.map(mach, 0.2, 0.3, 1.22, 1.25); + + // Reference area of rail button + final double outerArea = button.getTotalHeight() * button.getOuterDiameter(); + final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight(); + final double refArea = outerArea - notchArea; + + // Get "effective" CD + double calccd = cd * MathUtil.pow2(mach) * barrowmanObj.calculateStagnationCD(conditions.getMach()) * refArea / conditions.getRefArea() ; + + // Now compare with value from RailButtonCalc + WarningSet warnings = new WarningSet(); + AerodynamicForces assemblyForces = new AerodynamicForces().zero(); + AerodynamicForces componentForces = new AerodynamicForces(); + + double testcd = calcObj.calculatePressureCD(conditions, barrowmanObj.calculateStagnationCD(conditions.getMach()), 0, warnings); + + assertEquals("Calculated rail button CD incorrect", calccd, testcd, EPSILON); + } +} diff --git a/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java b/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java index 572b9a823..1c2d3306c 100644 --- a/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java +++ b/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java @@ -23,7 +23,7 @@ public class ThrustCurveMotorSetTest { private static final ThrustCurveMotor motor1 = new ThrustCurveMotor.Builder() .setManufacturer(Manufacturer.getManufacturer("A")) .setCommonName("F12") - .setDesignation("F12X") + .setDesignation("F12") .setDescription("Desc") .setMotorType(Motor.Type.UNKNOWN) .setStandardDelays(new double[] {}) @@ -38,7 +38,7 @@ public class ThrustCurveMotorSetTest { private static final ThrustCurveMotor motor2 = new ThrustCurveMotor.Builder() .setManufacturer(Manufacturer.getManufacturer("A")) .setCommonName("F12") - .setDesignation("F12H") + .setDesignation("F12") .setDescription("Desc") .setMotorType(Motor.Type.SINGLE) .setStandardDelays(new double[] { 5 }) @@ -51,20 +51,6 @@ public class ThrustCurveMotorSetTest { .build(); private static final ThrustCurveMotor motor3 = new ThrustCurveMotor.Builder() - .setManufacturer(Manufacturer.getManufacturer("A")) - .setCode("F12") - .setDescription("Desc") - .setMotorType(Motor.Type.UNKNOWN) - .setStandardDelays(new double[] { 0, Motor.PLUGGED_DELAY }) - .setDiameter(0.024) - .setLength(0.07) - .setTimePoints(new double[] { 0, 1, 2 }) - .setThrustPoints(new double[] { 0, 2, 0 }) - .setCGPoints(new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }) - .setDigest("digestC") - .build(); - - private static final ThrustCurveMotor motor4 = new ThrustCurveMotor.Builder() .setManufacturer(Manufacturer.getManufacturer("A")) .setDesignation("F12") .setDescription("Desc") @@ -113,39 +99,20 @@ public class ThrustCurveMotorSetTest { // Add motor2 assertTrue(set.matches(motor2)); set.addMotor(motor2); - assertEquals(motor1.getManufacturer(), set.getManufacturer()); - assertEquals(motor3.getCommonName(), set.getCommonName()); + assertEquals(motor2.getManufacturer(), set.getManufacturer()); + assertEquals(motor2.getCommonName(), set.getCommonName()); assertEquals(Motor.Type.SINGLE, set.getType()); - assertEquals(motor1.getDiameter(), set.getDiameter(), 0.00001); - assertEquals(motor1.getLength(), set.getLength(), 0.00001); + assertEquals(motor2.getDiameter(), set.getDiameter(), 0.00001); + assertEquals(motor2.getLength(), set.getLength(), 0.00001); assertEquals(2, set.getMotors().size()); - assertEquals(motor2, set.getMotors().get(0)); - assertEquals(motor1, set.getMotors().get(1)); + assertEquals(motor1, set.getMotors().get(0)); + assertEquals(motor2, set.getMotors().get(1)); assertEquals(Arrays.asList(5.0), set.getDelays()); - // Add motor3 - assertTrue(set.matches(motor3)); - set.addMotor(motor3); - assertEquals(motor1.getManufacturer(), set.getManufacturer()); - assertEquals(motor3.getCommonName(), set.getCommonName()); - assertEquals(Motor.Type.SINGLE, set.getType()); - assertEquals(motor1.getDiameter(), set.getDiameter(), 0.00001); - assertEquals(motor1.getLength(), set.getLength(), 0.00001); - assertEquals(3, set.getMotors().size()); - System.out.println("motor set"); - System.out.println(set.getMotors()); - System.out.println(motor3); - System.out.println(motor2); - System.out.println(motor1); - assertEquals(motor3, set.getMotors().get(0)); - assertEquals(motor2, set.getMotors().get(1)); - assertEquals(motor1, set.getMotors().get(2)); - assertEquals(Arrays.asList(0.0, 5.0, Motor.PLUGGED_DELAY), set.getDelays()); - - // Test that adding motor4 fails - assertFalse(set.matches(motor4)); + // Test that adding motor3 fails + assertFalse(set.matches(motor3)); try { - set.addMotor(motor4); + set.addMotor(motor3); fail("Did not throw exception"); } catch (IllegalArgumentException e) { } diff --git a/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java b/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java new file mode 100644 index 000000000..e02e8ce3a --- /dev/null +++ b/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java @@ -0,0 +1,398 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.document.OpenRocketDocumentFactory; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; +import net.sf.openrocket.util.MathUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class NoseConeTest extends BaseTestCase { + private final double EPSILON = MathUtil.EPSILON * 1000; + + @Test + public void testNormalNoseCone() { + NoseCone noseCone = new NoseCone(); + + // First set the parameters using the normal transition setters (i.e. using AftRadius and AftShoulder instead of Base and Shoulder) + noseCone.setType(Transition.Shape.OGIVE); + noseCone.setLength(0.06); + noseCone.setAftRadius(0.1); + noseCone.setAftShoulderLength(0.01); + noseCone.setAftShoulderRadius(0.05); + noseCone.setAftShoulderCapped(false); + noseCone.setAftShoulderThickness(0.001); + + assertEquals(Transition.Shape.OGIVE, noseCone.getType()); + assertEquals(0.06, noseCone.getLength(), EPSILON); + assertEquals(0.1, noseCone.getAftRadius(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); + assertEquals(0.1, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.01, noseCone.getAftShoulderLength(), EPSILON); + assertEquals(0.01, noseCone.getShoulderLength(), EPSILON); + assertEquals(0.05, noseCone.getAftShoulderRadius(), EPSILON); + assertEquals(0.05, noseCone.getShoulderRadius(), EPSILON); + assertFalse(noseCone.isAftShoulderCapped()); + assertFalse(noseCone.isShoulderCapped()); + assertEquals(0.001, noseCone.getAftShoulderThickness(), EPSILON); + assertEquals(0.001, noseCone.getShoulderThickness(), EPSILON); + assertFalse(noseCone.isAftRadiusAutomatic()); + + assertEquals(0, noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); + assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); + assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); + assertFalse(noseCone.isForeShoulderCapped()); + assertFalse(noseCone.isForeRadiusAutomatic()); + + // Test setting the specific nose cone setters + noseCone.setBaseRadius(0.2); + noseCone.setShoulderLength(0.03); + noseCone.setShoulderRadius(0.04); + noseCone.setShoulderCapped(true); + noseCone.setShoulderThickness(0.005); + + assertEquals(0.2, noseCone.getAftRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); + assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON); + assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); + assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON); + assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON); + assertTrue(noseCone.isAftShoulderCapped()); + assertTrue(noseCone.isShoulderCapped()); + assertEquals(0.005, noseCone.getAftShoulderThickness(), EPSILON); + assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON); + assertFalse(noseCone.isAftRadiusAutomatic()); + + assertEquals(0, noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); + assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); + assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); + assertFalse(noseCone.isForeShoulderCapped()); + assertFalse(noseCone.isForeRadiusAutomatic()); + } + + @Test + public void testFlippedNoseCone() { + NoseCone noseCone = new NoseCone(); + + // First set the parameters using the normal transition setters (i.e. using AftRadius and AftShoulder instead of Base and Shoulder) + noseCone.setType(Transition.Shape.OGIVE); + noseCone.setLength(0.06); + noseCone.setAftRadius(0.1); + noseCone.setAftShoulderLength(0.01); + noseCone.setAftShoulderRadius(0.05); + noseCone.setAftShoulderCapped(false); + noseCone.setAftShoulderThickness(0.001); + noseCone.setFlipped(true); + + assertEquals(Transition.Shape.OGIVE, noseCone.getType()); + assertEquals(0.06, noseCone.getLength(), EPSILON); + assertEquals(0.1, noseCone.getForeRadius(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); + assertEquals(0.1, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.01, noseCone.getForeShoulderLength(), EPSILON); + assertEquals(0.01, noseCone.getShoulderLength(), EPSILON); + assertEquals(0.05, noseCone.getForeShoulderRadius(), EPSILON); + assertEquals(0.05, noseCone.getShoulderRadius(), EPSILON); + assertFalse(noseCone.isForeShoulderCapped()); + assertFalse(noseCone.isShoulderCapped()); + assertEquals(0.001, noseCone.getForeShoulderThickness(), EPSILON); + assertEquals(0.001, noseCone.getShoulderThickness(), EPSILON); + assertFalse(noseCone.isForeRadiusAutomatic()); + + assertEquals(0, noseCone.getAftRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftShoulderLength(), EPSILON); + assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON); + assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON); + assertFalse(noseCone.isAftShoulderCapped()); + assertFalse(noseCone.isAftRadiusAutomatic()); + + // Test setting the specific nose cone setters + noseCone.setBaseRadius(0.2); + noseCone.setShoulderLength(0.03); + noseCone.setShoulderRadius(0.04); + noseCone.setShoulderCapped(true); + noseCone.setShoulderThickness(0.005); + + assertEquals(0.2, noseCone.getForeRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); + assertEquals(0.2, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.03, noseCone.getForeShoulderLength(), EPSILON); + assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); + assertEquals(0.04, noseCone.getForeShoulderRadius(), EPSILON); + assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON); + assertTrue(noseCone.isForeShoulderCapped()); + assertTrue(noseCone.isShoulderCapped()); + assertEquals(0.005, noseCone.getForeShoulderThickness(), EPSILON); + assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON); + assertFalse(noseCone.isForeRadiusAutomatic()); + + assertEquals(0, noseCone.getAftRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftShoulderLength(), EPSILON); + assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON); + assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON); + assertFalse(noseCone.isAftShoulderCapped()); + assertFalse(noseCone.isAftRadiusAutomatic()); + + // Flip back to normal + noseCone.setFlipped(false); + + assertEquals(0.2, noseCone.getAftRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); + assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON); + assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); + assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON); + assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON); + assertTrue(noseCone.isAftShoulderCapped()); + assertTrue(noseCone.isShoulderCapped()); + assertEquals(0.005, noseCone.getAftShoulderThickness(), EPSILON); + assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON); + assertFalse(noseCone.isAftRadiusAutomatic()); + + assertEquals(0, noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); + assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); + assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); + assertFalse(noseCone.isForeShoulderCapped()); + assertFalse(noseCone.isForeRadiusAutomatic()); + } + + @Test + public void testNormalNoseConeRadiusAutomatic() { + Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket(); + AxialStage stage = rocket.getStage(0); + + NoseCone noseCone = new NoseCone(Transition.Shape.CONICAL, 0.06, 0.01); + BodyTube tube1 = new BodyTube(0.06, 0.02); + tube1.setOuterRadiusAutomatic(false); + BodyTube tube2 = new BodyTube(0.06, 0.03); + tube2.setOuterRadiusAutomatic(false); + + // Test no previous or next component + stage.addChild(noseCone); + + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(stage, noseCone.getPreviousComponent()); + assertNull(noseCone.getPreviousSymmetricComponent()); + assertNull(noseCone.getNextComponent()); + assertNull(noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setAftRadiusAutomatic(true, true); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + noseCone.setForeRadiusAutomatic(true, true); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + + // Test with next component + stage.addChild(tube1); + + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(stage, noseCone.getPreviousComponent()); + assertNull(noseCone.getPreviousSymmetricComponent()); + assertSame(tube1, noseCone.getNextComponent()); + assertSame(tube1, noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setAftRadiusAutomatic(true, true); + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertTrue(noseCone.usesNextCompAutomatic()); + assertSame(stage, noseCone.getPreviousComponent()); + assertNull(noseCone.getPreviousSymmetricComponent()); + assertSame(tube1, noseCone.getNextComponent()); + assertSame(tube1, noseCone.getNextSymmetricComponent()); + assertTrue(noseCone.isAftRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setAftRadiusAutomatic(false, true); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setBaseRadiusAutomatic(true); + assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setForeRadiusAutomatic(true, true); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + + // Test with previous component + stage.addChild(tube2, 0); + + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertTrue(noseCone.usesNextCompAutomatic()); + assertSame(tube2, noseCone.getPreviousComponent()); + assertSame(tube2, noseCone.getPreviousSymmetricComponent()); + assertSame(tube1, noseCone.getNextComponent()); + assertSame(tube1, noseCone.getNextSymmetricComponent()); + assertTrue(noseCone.isAftRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + // Do a flip + noseCone.setFlipped(true); + assertTrue(noseCone.isForeRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isAftRadiusAutomatic()); + } + + @Test + public void testFlippedNoseConeRadiusAutomatic() { + Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket(); + AxialStage stage = rocket.getStage(0); + + NoseCone noseCone = new NoseCone(Transition.Shape.CONICAL, 0.06, 0.01); + noseCone.setFlipped(true); + BodyTube tube1 = new BodyTube(0.06, 0.02); + tube1.setOuterRadiusAutomatic(false); + BodyTube tube2 = new BodyTube(0.06, 0.03); + tube2.setOuterRadiusAutomatic(false); + + // Test no previous or next component + stage.addChild(noseCone); + + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(stage, noseCone.getPreviousComponent()); + assertNull(noseCone.getPreviousSymmetricComponent()); + assertNull(noseCone.getNextComponent()); + assertNull(noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setAftRadiusAutomatic(true, true); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + noseCone.setForeRadiusAutomatic(true, true); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + + // Test with previous component + stage.addChild(tube1, 0); + + assertFalse(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(tube1, noseCone.getPreviousComponent()); + assertSame(tube1, noseCone.getPreviousSymmetricComponent()); + assertNull(noseCone.getNextComponent()); + assertNull(noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertFalse(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isForeRadiusAutomatic()); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + + noseCone.setBaseRadiusAutomatic(true); + assertTrue(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(tube1, noseCone.getPreviousComponent()); + assertSame(tube1, noseCone.getPreviousSymmetricComponent()); + assertNull(noseCone.getNextComponent()); + assertNull(noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertTrue(noseCone.isForeRadiusAutomatic()); + assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + + noseCone.setForeRadiusAutomatic(false, true); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + + noseCone.setBaseRadiusAutomatic(true); + assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + + assertTrue(noseCone.isForeRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertFalse(noseCone.isAftRadiusAutomatic()); + + // Test with next component + stage.addChild(tube2); + + assertTrue(noseCone.usesPreviousCompAutomatic()); + assertFalse(noseCone.usesNextCompAutomatic()); + assertSame(tube1, noseCone.getPreviousComponent()); + assertSame(tube1, noseCone.getPreviousSymmetricComponent()); + assertSame(tube2, noseCone.getNextComponent()); + assertSame(tube2, noseCone.getNextSymmetricComponent()); + assertFalse(noseCone.isAftRadiusAutomatic()); + assertTrue(noseCone.isBaseRadiusAutomatic()); + assertTrue(noseCone.isForeRadiusAutomatic()); + assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); + } +} diff --git a/fileformat.txt b/fileformat.txt index 2a488f6cc..dafe8745a 100644 --- a/fileformat.txt +++ b/fileformat.txt @@ -58,6 +58,7 @@ The following file format versions exist: Added PhotoStudio settings saving () Added override CD parameter () Added stage activeness remembrance ( under ) + Added parameter for Nose Cones Separated into individual parameters for mass, CG, and CD. Rename to ( remains for backward compatibility) Rename to ( remains for backward compatibility) diff --git a/swing/resources/datafiles/examples/A simple model rocket.ork b/swing/resources/datafiles/examples/A simple model rocket.ork index b20c1d91e..3d2fb7bae 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/Boosted Dart.ork b/swing/resources/datafiles/examples/Boosted Dart.ork index b56c868d6..056e3e9c5 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/src/net/sf/openrocket/database/MotorDatabaseLoader.java b/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java index b073aee89..c5da0b415 100644 --- a/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java +++ b/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java @@ -127,8 +127,8 @@ public class MotorDatabaseLoader extends AsynchronousDatabaseLoader { new Pair( file.getName(), new BufferedInputStream(new FileInputStream(file)))); - } catch (IOException e) { - log.warn("IOException while reading " + file + ": " + e, e); + } catch (Exception e) { + log.warn("Exception while reading " + file + ": " + e, e); } } @@ -158,8 +158,8 @@ public class MotorDatabaseLoader extends AsynchronousDatabaseLoader { dialog.setVisible(true); } f.getV().close(); - } catch (IOException e) { - log.warn("IOException while loading file " + f.getU() + ": " + e, e); + } catch (Exception e) { + log.warn("Exception while loading file " + f.getU() + ": " + e, e); try { f.getV().close(); } catch (IOException e1) { @@ -178,7 +178,7 @@ public class MotorDatabaseLoader extends AsynchronousDatabaseLoader { FileIterator iterator; try { iterator = new DirectoryIterator(file, fileFilter, true); - } catch (IOException e) { + } catch (Exception e) { log.warn("Unable to read directory " + file + ": " + e, e); return; } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java index 9587d4760..7829ebf69 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java @@ -3,6 +3,8 @@ package net.sf.openrocket.gui.configdialog; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -40,7 +42,7 @@ public class NoseConeConfig extends RocketComponentConfig { private JLabel shapeLabel; private JSpinner shapeSpinner; private JSlider shapeSlider; - private final JCheckBox checkAutoAftRadius; + private final JCheckBox checkAutoBaseRadius; private static final Translator trans = Application.getTranslator(); // Prepended to the description from NoseCone.DESCRIPTIONS @@ -106,21 +108,21 @@ public class NoseConeConfig extends RocketComponentConfig { panel.add(new JLabel(trans.get("NoseConeCfg.lbl.Basediam"))); - final DoubleModel aftRadiusModel = new DoubleModel(component, "AftRadius", 2.0, UnitGroup.UNITS_LENGTH, 0); // Diameter = 2*Radius - final JSpinner radiusSpinner = new JSpinner(aftRadiusModel.getSpinnerModel()); + final DoubleModel baseRadius = new DoubleModel(component, "BaseRadius", 2.0, UnitGroup.UNITS_LENGTH, 0); // Diameter = 2*Radius + final JSpinner radiusSpinner = new JSpinner(baseRadius.getSpinnerModel()); radiusSpinner.setEditor(new SpinnerEditor(radiusSpinner)); panel.add(radiusSpinner, "growx"); order.add(((SpinnerEditor) radiusSpinner.getEditor()).getTextField()); - panel.add(new UnitSelector(aftRadiusModel), "growx"); - panel.add(new BasicSlider(aftRadiusModel.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap 0px"); + panel.add(new UnitSelector(baseRadius), "growx"); + panel.add(new BasicSlider(baseRadius.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap 0px"); - checkAutoAftRadius = new JCheckBox(aftRadiusModel.getAutomaticAction()); + checkAutoBaseRadius = new JCheckBox(baseRadius.getAutomaticAction()); //// Automatic - checkAutoAftRadius.setText(trans.get("NoseConeCfg.checkbox.Automatic")); - panel.add(checkAutoAftRadius, "skip, span 2, wrap"); - order.add(checkAutoAftRadius); - updateCheckboxAutoAftRadius(); + checkAutoBaseRadius.setText(trans.get("NoseConeCfg.checkbox.Automatic")); + panel.add(checkAutoBaseRadius, "skip, span 2, wrap"); + order.add(checkAutoBaseRadius); + updateCheckboxAutoBaseRadius(((NoseCone) component).isFlipped()); } {//// Wall thickness: @@ -142,10 +144,24 @@ public class NoseConeConfig extends RocketComponentConfig { //// Filled filledCheckbox.setText(trans.get("NoseConeCfg.checkbox.Filled")); filledCheckbox.setToolTipText(trans.get("NoseConeCfg.checkbox.Filled.ttip")); - panel.add(filledCheckbox, "skip, span 2, wrap"); + panel.add(filledCheckbox, "skip, span 2, wrap para"); order.add(filledCheckbox); } + {//// Flip to tail cone: + final JCheckBox flipCheckbox = new JCheckBox(new BooleanModel(component, "Flipped")); + flipCheckbox.setText(trans.get("NoseConeCfg.checkbox.Flip")); + flipCheckbox.setToolTipText(trans.get("NoseConeCfg.checkbox.Flip.ttip")); + panel.add(flipCheckbox, "spanx, wrap"); + order.add(flipCheckbox); + flipCheckbox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + updateCheckboxAutoBaseRadius(e.getStateChange() == ItemEvent.SELECTED); + } + }); + } + panel.add(new JLabel(""), "growy"); //// Description @@ -189,25 +205,29 @@ public class NoseConeConfig extends RocketComponentConfig { * Sets the checkAutoAftRadius checkbox's enabled state and tooltip text, based on the state of its next component. * If there is no next symmetric component or if that component already has its auto checkbox checked, the * checkAutoAftRadius checkbox is disabled. + * + * @param isFlipped whether the nose cone is flipped */ - private void updateCheckboxAutoAftRadius() { - if (component == null || checkAutoAftRadius == null) return; + private void updateCheckboxAutoBaseRadius(boolean isFlipped) { + if (component == null || checkAutoBaseRadius == null) return; // Disable check button if there is no component to get the diameter from - SymmetricComponent nextComp = ((NoseCone) component).getNextSymmetricComponent(); - if (nextComp == null) { - checkAutoAftRadius.setEnabled(false); - ((NoseCone) component).setAftRadiusAutomatic(false); - checkAutoAftRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic_noReferenceComponent")); + NoseCone noseCone = ((NoseCone) component); + SymmetricComponent referenceComp = isFlipped ? noseCone.getPreviousSymmetricComponent() : noseCone.getNextSymmetricComponent(); + if (referenceComp == null) { + checkAutoBaseRadius.setEnabled(false); + ((NoseCone) component).setBaseRadiusAutomatic(false); + checkAutoBaseRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic_noReferenceComponent")); return; } - if (!nextComp.usesPreviousCompAutomatic()) { - checkAutoAftRadius.setEnabled(true); - checkAutoAftRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic")); + if ((!isFlipped&& !referenceComp.usesPreviousCompAutomatic()) || + isFlipped && !referenceComp.usesNextCompAutomatic()) { + checkAutoBaseRadius.setEnabled(true); + checkAutoBaseRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic")); } else { - checkAutoAftRadius.setEnabled(false); - ((NoseCone) component).setAftRadiusAutomatic(false); - checkAutoAftRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic_alreadyAuto")); + checkAutoBaseRadius.setEnabled(false); + ((NoseCone) component).setBaseRadiusAutomatic(false); + checkAutoBaseRadius.setToolTipText(trans.get("NoseConeCfg.checkbox.ttip.Automatic_alreadyAuto")); } } } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index a174f317f..1cf1bdbf0 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -613,157 +613,170 @@ public class RocketComponentConfig extends JPanel { protected JPanel shoulderTab() { JPanel panel = new JPanel(new MigLayout("fill")); - JPanel sub; - DoubleModel m, m2; DoubleModel m0 = new DoubleModel(0); - BooleanModel bm; - JCheckBox check; - JSpinner spin; - //// Fore shoulder, not for NoseCone - if (!(component instanceof NoseCone)) { - sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); - - //// Fore shoulder - sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder"))); - - - //// Radius - //// Diameter: - sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter"))); - - m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0); - m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - sub.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - - sub.add(new UnitSelector(m), "growx"); - sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); - - - //// Length: - sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length"))); - - m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - sub.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - - sub.add(new UnitSelector(m), "growx"); - sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap"); - - - //// Thickness: - sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness"))); - - m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0); - m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - sub.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - - sub.add(new UnitSelector(m), "growx"); - sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); - - - //// Capped - bm = new BooleanModel(component, "ForeShoulderCapped"); - check = new JCheckBox(bm); - //// End capped - check.setText(trans.get("RocketCompCfg.checkbox.Endcapped")); - check.setToolTipText(trans.get("RocketCompCfg.checkbox.Endcapped.ttip")); - sub.add(check, "spanx"); - order.add(check); - - - panel.add(sub); + addForeShoulderSection(panel, m0); } - - + //// Aft shoulder + addAftShoulderSection(panel, m0); + + return panel; + } + + private void addForeShoulderSection(JPanel panel, DoubleModel m0) { + DoubleModel m; + JCheckBox check; + JPanel sub; + DoubleModel m2; + JSpinner spin; + BooleanModel bm; sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); - - if (component instanceof NoseCone) - //// Nose cone shoulder - sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder"))); - else - //// Aft shoulder - sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder"))); - - + + //// Fore shoulder + sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder"))); + + //// Radius //// Diameter: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter"))); - - m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0); - m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH); - + + m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0); + m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - + sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); - - + + //// Length: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length"))); - - m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0); - + + m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0); + spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - + sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap"); - - + + //// Thickness: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness"))); - - m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0); - m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH); - + + m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0); + m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - + sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); - - + + //// Capped - bm = new BooleanModel(component, "AftShoulderCapped"); + bm = new BooleanModel(component, "ForeShoulderCapped"); check = new JCheckBox(bm); //// End capped check.setText(trans.get("RocketCompCfg.checkbox.Endcapped")); check.setToolTipText(trans.get("RocketCompCfg.checkbox.Endcapped.ttip")); sub.add(check, "spanx"); order.add(check); - - + panel.add(sub); - - - return panel; } - - - - + + private void addAftShoulderSection(JPanel panel, DoubleModel m0) { + JSpinner spin; + JCheckBox check; + DoubleModel m; + DoubleModel m2; + JPanel sub; + BooleanModel bm; + sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); + + String valueNameShoulder = "AftShoulder"; + String valueNameRadius = "AftRadius"; + + if (component instanceof NoseCone) { + // Nose cones have a special shoulder method to cope with flipped nose cones + valueNameShoulder = "Shoulder"; + valueNameRadius = "BaseRadius"; + //// Nose cone shoulder + sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder"))); + } else { + //// Aft shoulder + sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder"))); + } + + + //// Radius + //// Diameter: + sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter"))); + + m = new DoubleModel(component, valueNameShoulder+"Radius", 2, UnitGroup.UNITS_LENGTH, 0); + m2 = new DoubleModel(component, valueNameRadius, 2, UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "growx"); + order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + + sub.add(new UnitSelector(m), "growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); + + + //// Length: + sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length"))); + + m = new DoubleModel(component, valueNameShoulder+"Length", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "growx"); + order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + + sub.add(new UnitSelector(m), "growx"); + sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap"); + + + //// Thickness: + sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness"))); + + m = new DoubleModel(component, valueNameShoulder+"Thickness", UnitGroup.UNITS_LENGTH, 0); + m2 = new DoubleModel(component, valueNameShoulder+"Radius", UnitGroup.UNITS_LENGTH); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "growx"); + order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + + sub.add(new UnitSelector(m), "growx"); + sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); + + + //// Capped + bm = new BooleanModel(component, valueNameShoulder+"Capped"); + check = new JCheckBox(bm); + //// End capped + check.setText(trans.get("RocketCompCfg.checkbox.Endcapped")); + check.setToolTipText(trans.get("RocketCompCfg.checkbox.Endcapped.ttip")); + sub.add(check, "spanx"); + order.add(check); + + panel.add(sub); + } + /* * Private inner class to handle events in componentNameField. */ diff --git a/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java index 6487e5059..b94fae163 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java @@ -228,18 +228,16 @@ public class TransitionConfig extends RocketComponentConfig { private void updateCheckboxAutoAftRadius() { if (component == null || checkAutoAftRadius == null) return; - // Disable check button if there is no component to get the diameter from - SymmetricComponent nextComp = ((Transition) component).getNextSymmetricComponent(); - if (nextComp == null) { + Transition transition = (Transition) component; + boolean enabled = transition.canUseNextCompAutomatic(); + if (enabled) { // Can use auto radius + checkAutoAftRadius.setEnabled(true); + checkAutoAftRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic")); + } else if (transition.getNextSymmetricComponent() == null) { // No next component to take the auto radius from checkAutoAftRadius.setEnabled(false); ((Transition) component).setAftRadiusAutomatic(false); checkAutoAftRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic_noReferenceComponent")); - return; - } - if (!nextComp.usesPreviousCompAutomatic()) { - checkAutoAftRadius.setEnabled(true); - checkAutoAftRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic")); - } else { + } else { // Next component already has its auto radius checked checkAutoAftRadius.setEnabled(false); ((Transition) component).setAftRadiusAutomatic(false); checkAutoAftRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic_alreadyAuto")); @@ -254,18 +252,16 @@ public class TransitionConfig extends RocketComponentConfig { private void updateCheckboxAutoForeRadius() { if (component == null || checkAutoForeRadius == null) return; - // Disable check button if there is no component to get the diameter from - SymmetricComponent prevComp = ((Transition) component).getPreviousSymmetricComponent(); - if (prevComp == null) { + Transition transition = (Transition) component; + boolean enabled = transition.canUsePreviousCompAutomatic(); + if (enabled) { // Can use auto radius + checkAutoForeRadius.setEnabled(true); + checkAutoForeRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic")); + } else if (transition.getPreviousSymmetricComponent() == null) { // No next component to take the auto radius from checkAutoForeRadius.setEnabled(false); ((Transition) component).setForeRadiusAutomatic(false); checkAutoForeRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic_noReferenceComponent")); - return; - } - if (!prevComp.usesNextCompAutomatic()) { - checkAutoForeRadius.setEnabled(true); - checkAutoForeRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic")); - } else { + } else { // Next component already has its auto radius checked checkAutoForeRadius.setEnabled(false); ((Transition) component).setForeRadiusAutomatic(false); checkAutoForeRadius.setToolTipText(trans.get("TransitionCfg.checkbox.ttip.Automatic_alreadyAuto")); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java index 4683a6a15..763f873b0 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java @@ -27,6 +27,7 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.URLLabel; import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogLevelBufferLogger; import net.sf.openrocket.logging.LogLine; @@ -44,6 +45,7 @@ public class BugReportDialog extends JDialog { private static final String REPORT_EMAIL_URL = "mailto:" + REPORT_EMAIL; private static final Translator trans = Application.getTranslator(); + private static final SwingPreferences preferences = (SwingPreferences) Application.getPreferences(); public BugReportDialog(Window parent, String labelText, final String message, final boolean sendIfUnchanged) { @@ -108,31 +110,10 @@ public class BugReportDialog extends JDialog { * @param parent the parent window (may be null). */ public static void showBugReportDialog(Window parent) { - StringBuilder sb = new StringBuilder(); - - sb.append("---------- Bug report ----------\n"); - sb.append('\n'); - sb.append("Include detailed steps on how to trigger the bug:\n"); - sb.append("(You can edit text directly in this window)\n"); - sb.append('\n'); - sb.append("1. \n"); - sb.append("2. \n"); - sb.append("3. \n"); - sb.append('\n'); - - sb.append("What does the software do and what in your opinion should it do in the " + - "case described above:\n"); - sb.append('\n'); - sb.append('\n'); - sb.append('\n'); - - sb.append("Include your email address (optional; it helps if we can " + - "contact you in case we need additional information):\n"); - sb.append('\n'); - sb.append('\n'); - sb.append('\n'); - + + // ---------- Bug report ---------- + addBugReportInformation(sb); sb.append("(Do not modify anything below this line.)\n"); sb.append("---------- System information ----------\n"); @@ -157,28 +138,9 @@ public class BugReportDialog extends JDialog { */ public static void showExceptionDialog(Window parent, Thread t, Throwable e) { StringBuilder sb = new StringBuilder(); - - sb.append("---------- Bug report ----------\n"); - sb.append('\n'); - sb.append("Please include a description about what actions you were " + - "performing when the exception occurred:\n"); - sb.append("(You can edit text directly in this window)\n"); - sb.append('\n'); - sb.append("1. \n"); - sb.append("2. \n"); - sb.append("3. \n"); - sb.append("\n"); - sb.append("If possible, please send us the .ork file that caused the bug.\n"); - sb.append('\n'); - - - sb.append("Include your email address (optional; it helps if we can " + - "contact you in case we need additional information):\n"); - sb.append('\n'); - sb.append('\n'); - sb.append('\n'); - sb.append('\n'); + // ---------- Bug report ---------- + addBugReportInformation(sb); sb.append("(Do not modify anything below this line.)\n"); sb.append("---------- Exception stack trace ----------\n"); @@ -211,12 +173,37 @@ public class BugReportDialog extends JDialog { new BugReportDialog(parent, trans.get("bugreport.reportDialog.txt2"), sb.toString(), true); reportDialog.setVisible(true); } + + private static void addBugReportInformation(StringBuilder sb) { + sb.append("---------- Bug report ----------\n"); + sb.append('\n'); + sb.append("Please include a description about what actions you were " + + "performing when the exception occurred:\n"); + sb.append("(You can edit text directly in this window)\n"); + sb.append('\n'); + sb.append("1. \n"); + sb.append("2. \n"); + sb.append("3. \n"); + + sb.append("\n"); + sb.append("If possible, please send us the .ork file that caused the bug.\n"); + sb.append('\n'); + + + sb.append("Include your email address (optional; it helps if we can " + + "contact you in case we need additional information):\n"); + sb.append('\n'); + sb.append('\n'); + sb.append('\n'); + sb.append('\n'); + } private static void addSystemInformation(StringBuilder sb) { StringBuilder sbTemp = new StringBuilder(); sbTemp.append("OpenRocket version: " + BuildProperties.getVersion() + "\n"); sbTemp.append("OpenRocket source: " + BuildProperties.getBuildSource() + "\n"); sbTemp.append("OpenRocket location: " + JarUtil.getCurrentJarFile() + "\n"); + sbTemp.append("User-defined thrust curves location: " + preferences.getUserThrustCurveFilesAsString() + "\n"); sbTemp.append("JOGL version: " + JoglVersion.getInstance().getImplementationVersion() + "\n"); sbTemp.append("Current default locale: " + Locale.getDefault() + "\n"); sbTemp.append("System properties:\n"); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java index 0fd20a25d..111f16378 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java @@ -83,7 +83,7 @@ public class ScaleDialog extends JDialog { // SymmetricComponent addScaler(SymmetricComponent.class, "Thickness", "isFilled", SCALERS_NO_OFFSET); - // Transition + Nose cone + // Transition addScaler(Transition.class, "ForeRadius", "isForeRadiusAutomatic", SCALERS_NO_OFFSET); addScaler(Transition.class, "AftRadius", "isAftRadiusAutomatic", SCALERS_NO_OFFSET); addScaler(Transition.class, "ForeShoulderRadius", SCALERS_NO_OFFSET); @@ -92,6 +92,12 @@ public class ScaleDialog extends JDialog { addScaler(Transition.class, "AftShoulderRadius", SCALERS_NO_OFFSET); addScaler(Transition.class, "AftShoulderThickness", SCALERS_NO_OFFSET); addScaler(Transition.class, "AftShoulderLength", SCALERS_NO_OFFSET); + + // Nose cone + addScaler(NoseCone.class, "BaseRadius", "isBaseRadiusAutomatic", SCALERS_NO_OFFSET); + addScaler(NoseCone.class, "ShoulderRadius", SCALERS_NO_OFFSET); + addScaler(NoseCone.class, "ShoulderThickness", SCALERS_NO_OFFSET); + addScaler(NoseCone.class, "ShoulderLength", SCALERS_NO_OFFSET); // Body tube addScaler(BodyTube.class, "OuterRadius", "isOuterRadiusAutomatic", SCALERS_NO_OFFSET); @@ -559,6 +565,10 @@ public class ScaleDialog extends JDialog { } Collections.reverse(classes); // Always do the super component scales first (can cause problems otherwise in the scale order) for (Class cl : classes) { + // Don't use the super-class methods of transitions for nose cones + if (cl == Transition.class && component instanceof NoseCone) { + continue; + } List list = SCALERS_NO_OFFSET.get(cl); if (list != null && list.size() > 0) { for (Scaler s : list) { diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java index 9631603a1..de73270f9 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java @@ -86,14 +86,7 @@ public class GeneralPreferencesPanel extends PreferencesPanel { //// User-defined thrust curves: this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap"); final JTextField field = new JTextField(); - List files = preferences.getUserThrustCurveFiles(); - String str = ""; - for (File file : files) { - if (str.length() > 0) { - str += ";"; - } - str += file.getAbsolutePath(); - } + String str = preferences.getUserThrustCurveFilesAsString(); field.setText(str); field.getDocument().addDocumentListener(new DocumentListener() { @Override 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 2dad7795d..a852e94b7 100644 --- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java +++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java @@ -1,7 +1,9 @@ package net.sf.openrocket.gui.main.componenttree; import java.awt.Component; +import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Font; import java.util.List; import javax.swing.JLabel; @@ -80,6 +82,8 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { p.setToolTipText(getToolTipSingleComponent(c)); } + Font originalFont = tree.getFont(); + p.setFont(originalFont); comp = p; } diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java index 4a42ffdb9..45c5ce476 100644 --- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -304,6 +304,24 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { return list; } + + /** + * Returns the files/directories to be loaded as custom thrust curves, formatting as a string. If there are multiple + * locations, they are separated by a semicolon. + * + * @return a list of files to load as thrust curves, formatted as a semicolon separated string. + */ + public String getUserThrustCurveFilesAsString() { + List files = getUserThrustCurveFiles(); + StringBuilder sb = new StringBuilder(); + for (File file : files) { + if (sb.length() > 0) { + sb.append(";"); + } + sb.append(file.getAbsolutePath()); + } + return sb.toString(); + } public File getDefaultUserThrustCurveFile() { File appdir = SystemInfo.getUserApplicationDirectory();