diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index e1e9dd23d..d77e844cd 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1148,6 +1148,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 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/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/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/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/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) {