Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / net / sf / openrocket / rocketcomponent / Transition.java
index f3a6f9b6a8764925dcb53939fc0244922774da28..6b12f156ec15bc6963d109b0ffaa4cf453b67503 100644 (file)
@@ -1,28 +1,31 @@
 package net.sf.openrocket.rocketcomponent;
 
-import static java.lang.Math.sin;
-import static net.sf.openrocket.util.MathUtil.*;
-
-import java.util.Collection;
-
 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 java.util.Collection;
+
+import static java.lang.Math.sin;
+import static net.sf.openrocket.util.MathUtil.pow2;
+import static net.sf.openrocket.util.MathUtil.pow3;
+
 
 public class Transition extends SymmetricComponent {
        private static final Translator trans = Application.getTranslator();
        private static final double CLIP_PRECISION = 0.0001;
-       
+
 
        private Shape type;
        private double shapeParameter;
        private boolean clipped; // Not to be read - use isClipped(), which may be overriden
-       
+
        private double radius1, radius2;
        private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic
-                       
+
 
        private double foreShoulderRadius;
        private double foreShoulderThickness;
@@ -32,30 +35,39 @@ public class Transition extends SymmetricComponent {
        private double aftShoulderThickness;
        private double aftShoulderLength;
        private boolean aftShoulderCapped;
-       
+
 
        // Used to cache the clip length
        private double clipLength = -1;
-       
+
        public Transition() {
                super();
-               
+
                this.radius1 = DEFAULT_RADIUS;
                this.radius2 = DEFAULT_RADIUS;
                this.length = DEFAULT_RADIUS * 3;
                this.autoRadius1 = true;
                this.autoRadius2 = true;
-               
+
                this.type = Shape.CONICAL;
                this.shapeParameter = 0;
                this.clipped = true;
        }
-       
-       
+
+       ////////  Length  ////////
+       @Override
+       public void setLength( double length ) {
+               if ( this.length == length ) {
+                       return;
+               }
+               // Need to clearPreset when length changes.
+               clearPreset();
+               super.setLength( length );
+       }
 
 
        ////////  Fore radius  ////////
-       
+
 
        @Override
        public double getForeRadius() {
@@ -72,35 +84,39 @@ public class Transition extends SymmetricComponent {
                }
                return radius1;
        }
-       
+
        public void setForeRadius(double radius) {
                if ((this.radius1 == radius) && (autoRadius1 == false))
                        return;
-               
+
                this.autoRadius1 = false;
                this.radius1 = Math.max(radius, 0);
-               
+
                if (this.thickness > this.radius1 && this.thickness > this.radius2)
                        this.thickness = Math.max(this.radius1, this.radius2);
+
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        @Override
        public boolean isForeRadiusAutomatic() {
                return autoRadius1;
        }
-       
+
        public void setForeRadiusAutomatic(boolean auto) {
                if (autoRadius1 == auto)
                        return;
-               
+
                autoRadius1 = auto;
+
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
-       
+
+
        ////////  Aft radius  /////////
-       
+
        @Override
        public double getAftRadius() {
                if (isAftRadiusAutomatic()) {
@@ -116,62 +132,66 @@ public class Transition extends SymmetricComponent {
                }
                return radius2;
        }
-       
-       
+
+
 
        public void setAftRadius(double radius) {
                if ((this.radius2 == radius) && (autoRadius2 == false))
                        return;
-               
+
                this.autoRadius2 = false;
                this.radius2 = Math.max(radius, 0);
-               
+
                if (this.thickness > this.radius1 && this.thickness > this.radius2)
                        this.thickness = Math.max(this.radius1, this.radius2);
+
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        @Override
        public boolean isAftRadiusAutomatic() {
                return autoRadius2;
        }
-       
+
        public void setAftRadiusAutomatic(boolean auto) {
                if (autoRadius2 == auto)
                        return;
-               
+
                autoRadius2 = auto;
+
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
-       
+
+
 
        //// Radius automatics
-       
+
        @Override
        protected double getFrontAutoRadius() {
                if (isAftRadiusAutomatic())
                        return -1;
                return getAftRadius();
        }
-       
-       
+
+
        @Override
        protected double getRearAutoRadius() {
                if (isForeRadiusAutomatic())
                        return -1;
                return getForeRadius();
        }
-       
-       
+
+
 
 
        ////////  Type & shape  /////////
-       
+
        public Shape getType() {
                return type;
        }
-       
+
        public void setType(Shape type) {
                if (type == null) {
                        throw new IllegalArgumentException("setType called with null argument");
@@ -183,142 +203,144 @@ public class Transition extends SymmetricComponent {
                this.shapeParameter = type.defaultParameter();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public double getShapeParameter() {
                return shapeParameter;
        }
-       
+
        public void setShapeParameter(double n) {
                if (shapeParameter == n)
                        return;
                this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter());
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public boolean isClipped() {
                if (!type.isClippable())
                        return false;
                return clipped;
        }
-       
+
        public void setClipped(boolean c) {
                if (clipped == c)
                        return;
                clipped = c;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public boolean isClippedEnabled() {
                return type.isClippable();
        }
-       
+
        public double getShapeParameterMin() {
                return type.minParameter();
        }
-       
+
        public double getShapeParameterMax() {
                return type.maxParameter();
        }
-       
-       
+
+
        ////////  Shoulders  ////////
-       
+
        public double getForeShoulderRadius() {
                return foreShoulderRadius;
        }
-       
+
        public void setForeShoulderRadius(double foreShoulderRadius) {
                if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
                        return;
                this.foreShoulderRadius = foreShoulderRadius;
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getForeShoulderThickness() {
                return foreShoulderThickness;
        }
-       
+
        public void setForeShoulderThickness(double foreShoulderThickness) {
                if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness))
                        return;
                this.foreShoulderThickness = foreShoulderThickness;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getForeShoulderLength() {
                return foreShoulderLength;
        }
-       
+
        public void setForeShoulderLength(double foreShoulderLength) {
                if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength))
                        return;
                this.foreShoulderLength = foreShoulderLength;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public boolean isForeShoulderCapped() {
                return foreShoulderCapped;
        }
-       
+
        public void setForeShoulderCapped(boolean capped) {
                if (this.foreShoulderCapped == capped)
                        return;
                this.foreShoulderCapped = capped;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
-       
+
+
 
 
        public double getAftShoulderRadius() {
                return aftShoulderRadius;
        }
-       
+
        public void setAftShoulderRadius(double aftShoulderRadius) {
                if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
                        return;
                this.aftShoulderRadius = aftShoulderRadius;
+               clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getAftShoulderThickness() {
                return aftShoulderThickness;
        }
-       
+
        public void setAftShoulderThickness(double aftShoulderThickness) {
                if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness))
                        return;
                this.aftShoulderThickness = aftShoulderThickness;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getAftShoulderLength() {
                return aftShoulderLength;
        }
-       
+
        public void setAftShoulderLength(double aftShoulderLength) {
                if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength))
                        return;
                this.aftShoulderLength = aftShoulderLength;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public boolean isAftShoulderCapped() {
                return aftShoulderCapped;
        }
-       
+
        public void setAftShoulderCapped(boolean capped) {
                if (this.aftShoulderCapped == capped)
                        return;
                this.aftShoulderCapped = capped;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
-       
+
+
 
 
        ///////////   Shape implementations   ////////////
-       
+
 
 
        /**
@@ -328,20 +350,20 @@ public class Transition extends SymmetricComponent {
        public double getRadius(double x) {
                if (x < 0 || x > length)
                        return 0;
-               
+
                double r1 = getForeRadius();
                double r2 = getAftRadius();
-               
+
                if (r1 == r2)
                        return r1;
-               
+
                if (r1 > r2) {
                        x = length - x;
                        double tmp = r1;
                        r1 = r2;
                        r2 = tmp;
                }
-               
+
                if (isClipped()) {
                        // Check clip calculation
                        if (clipLength < 0)
@@ -352,7 +374,7 @@ public class Transition extends SymmetricComponent {
                        return r1 + type.getRadius(x, r2 - r1, length, shapeParameter);
                }
        }
-       
+
        /**
         * Numerically solve clipLength from the equation
         *     r1 == type.getRadius(clipLength,r2,clipLength+length)
@@ -360,27 +382,27 @@ public class Transition extends SymmetricComponent {
         */
        private void calculateClip(double r1, double r2) {
                double min = 0, max = length;
-               
+
                if (r1 >= r2) {
                        double tmp = r1;
                        r1 = r2;
                        r2 = tmp;
                }
-               
+
                if (r1 == 0) {
                        clipLength = 0;
                        return;
                }
-               
+
                if (length <= 0) {
                        clipLength = 0;
                        return;
                }
-               
+
                // Required:
                //    getR(min,min+length,r2) - r1 < 0
                //    getR(max,max+length,r2) - r1 > 0
-               
+
                int n = 0;
                while (type.getRadius(max, r2, max + length, shapeParameter) - r1 < 0) {
                        min = max;
@@ -389,7 +411,7 @@ public class Transition extends SymmetricComponent {
                        if (n > 10)
                                break;
                }
-               
+
                while (true) {
                        clipLength = (min + max) / 2;
                        if ((max - min) < CLIP_PRECISION)
@@ -402,14 +424,14 @@ public class Transition extends SymmetricComponent {
                        }
                }
        }
-       
-       
+
+
        @Override
        public double getInnerRadius(double x) {
                return Math.max(getRadius(x) - thickness, 0);
        }
-       
-       
+
+
 
        @Override
        public Collection<Coordinate> getComponentBounds() {
@@ -420,33 +442,33 @@ public class Transition extends SymmetricComponent {
                        addBound(bounds, getLength() + aftShoulderLength, aftShoulderRadius);
                return bounds;
        }
-       
+
        @Override
-       public double getComponentMass() {
-               double mass = super.getComponentMass();
+       public double getComponentVolume() {
+               double volume =  super.getComponentVolume();
                if (getForeShoulderLength() > 0.001) {
                        final double or = getForeShoulderRadius();
                        final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
-                       mass += ringMass(or, ir, getForeShoulderLength(), getMaterial().getDensity());
+                       volume += ringVolume( or, ir, getForeShoulderLength() );
                }
                if (isForeShoulderCapped()) {
                        final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
-                       mass += ringMass(ir, 0, getForeShoulderThickness(), getMaterial().getDensity());
+                       volume += ringVolume(ir, 0, getForeShoulderThickness() );
                }
-               
+
                if (getAftShoulderLength() > 0.001) {
                        final double or = getAftShoulderRadius();
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
-                       mass += ringMass(or, ir, getAftShoulderLength(), getMaterial().getDensity());
+                       volume += ringVolume(or, ir, getAftShoulderLength() );
                }
                if (isAftShoulderCapped()) {
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
-                       mass += ringMass(ir, 0, getAftShoulderThickness(), getMaterial().getDensity());
+                       volume += ringVolume(ir, 0, getAftShoulderThickness() );
                }
-               
-               return mass;
+
+               return volume;
        }
-       
+
        @Override
        public Coordinate getComponentCG() {
                Coordinate cg = super.getComponentCG();
@@ -461,7 +483,7 @@ public class Transition extends SymmetricComponent {
                                        getForeShoulderThickness() - getForeShoulderLength(),
                                        getMaterial().getDensity()));
                }
-               
+
                if (getAftShoulderLength() > 0.001) {
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
                        cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(),
@@ -475,8 +497,8 @@ public class Transition extends SymmetricComponent {
                }
                return cg;
        }
-       
-       
+
+
        /*
         * The moments of inertia are not explicitly corrected for the shoulders.
         * However, since the mass is corrected, the inertia is automatically corrected
@@ -493,13 +515,13 @@ public class Transition extends SymmetricComponent {
                //// Transition
                return trans.get("Transition.Transition");
        }
-       
+
        @Override
        protected void componentChanged(ComponentChangeEvent e) {
                super.componentChanged(e);
                clipLength = -1;
        }
-       
+
        /**
         * Check whether the given type can be added to this component.  Transitions allow any
         * InternalComponents to be added.
@@ -513,8 +535,63 @@ public class Transition extends SymmetricComponent {
                        return true;
                return false;
        }
-       
-       
+
+       @Override
+       public Type getPresetType() {
+               return ComponentPreset.Type.TRANSITION;
+       }
+
+
+       @Override
+       protected void loadFromPreset(ComponentPreset preset) {
+
+               boolean presetFilled = false;
+               if ( preset.has(ComponentPreset.FILLED ) ) {
+                       presetFilled = preset.get( ComponentPreset.FILLED);
+               }
+
+               if ( preset.has(ComponentPreset.SHAPE) ) {
+                       Shape s = preset.get(ComponentPreset.SHAPE);
+                       this.setType(s);
+               }
+               if ( preset.has(ComponentPreset.AFT_OUTER_DIAMETER) )  {
+                       double outerDiameter = preset.get(ComponentPreset.AFT_OUTER_DIAMETER);
+                       this.setAftRadiusAutomatic(false);
+                       this.setAftRadius(outerDiameter/2.0);
+               }
+               if ( preset.has(ComponentPreset.AFT_SHOULDER_LENGTH) ) {
+                       double d = preset.get(ComponentPreset.AFT_SHOULDER_LENGTH);
+                       this.setAftShoulderLength(d);
+               }
+               if ( preset.has(ComponentPreset.AFT_SHOULDER_DIAMETER) ) {
+                       double d = preset.get(ComponentPreset.AFT_SHOULDER_DIAMETER);
+                       this.setAftShoulderRadius(d/2.0);
+                       if ( presetFilled ) {
+                               this.setAftShoulderThickness(d/2.0);
+                       }
+               }
+               if ( preset.has(ComponentPreset.FORE_OUTER_DIAMETER) )  {
+                       double outerDiameter = preset.get(ComponentPreset.FORE_OUTER_DIAMETER);
+                       this.setForeRadiusAutomatic(false);
+                       this.setForeRadius(outerDiameter/2.0);
+               }
+               if ( preset.has(ComponentPreset.FORE_SHOULDER_LENGTH) ) {
+                       double d = preset.get(ComponentPreset.FORE_SHOULDER_LENGTH);
+                       this.setForeShoulderLength(d);
+               }
+               if ( preset.has(ComponentPreset.FORE_SHOULDER_DIAMETER) ) {
+                       double d = preset.get(ComponentPreset.FORE_SHOULDER_DIAMETER);
+                       this.setForeShoulderRadius(d/2.0);
+                       if ( presetFilled ) {
+                               this.setForeShoulderThickness(d/2.0);
+                       }
+               }
+
+               super.loadFromPreset(preset);
+
+               fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
+
+       }
 
        /**
         * An enumeration listing the possible shapes of transitions.
@@ -522,7 +599,7 @@ public class Transition extends SymmetricComponent {
         * @author Sampo Niskanen <sampo.niskanen@iki.fi>
         */
        public static enum Shape {
-               
+
                /**
                 * Conical shape.
                 */
@@ -540,7 +617,7 @@ public class Transition extends SymmetricComponent {
                                return radius * x / length;
                        }
                },
-               
+
                /**
                 * Ogive shape.  The shape parameter is the portion of an extended tangent ogive
                 * that will be used.  That is, for param==1 a tangent ogive will be produced, and
@@ -556,12 +633,12 @@ public class Transition extends SymmetricComponent {
                        public boolean usesParameter() {
                                return true; // Range 0...1 is default
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 1.0; // Tangent ogive by default
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -569,17 +646,17 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 1;
-                               
+
                                // Impossible to calculate ogive for length < radius, scale instead
                                // TODO: LOW: secant ogive could be calculated lower
                                if (length < radius) {
                                        x = x * radius / length;
                                        length = radius;
                                }
-                               
+
                                if (param < 0.001)
                                        return CONICAL.getRadius(x, radius, length, param);
-                               
+
                                // Radius of circle is:
                                double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
                                                (pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
@@ -589,7 +666,7 @@ public class Transition extends SymmetricComponent {
                                return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
                        }
                },
-               
+
                /**
                 * Ellipsoidal shape.
                 */
@@ -597,7 +674,7 @@ public class Transition extends SymmetricComponent {
                ELLIPSOID(trans.get("Shape.Ellipsoid"),
                                //// An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
                                trans.get("Shape.Ellipsoid.desc1"),
-                               //// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.         
+                               //// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
                                trans.get("Shape.Ellipsoid.desc2"), true) {
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
@@ -608,7 +685,7 @@ public class Transition extends SymmetricComponent {
                                return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
                        }
                },
-               
+
                //// Power series
                POWER(trans.get("Shape.Powerseries"),
                                trans.get("Shape.Powerseries.desc1"),
@@ -617,12 +694,12 @@ public class Transition extends SymmetricComponent {
                        public boolean usesParameter() { // Range 0...1
                                return true;
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 0.5;
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -638,29 +715,29 @@ public class Transition extends SymmetricComponent {
                                }
                                return radius * Math.pow(x / length, param);
                        }
-                       
+
                },
-               
+
                //// Parabolic series
                PARABOLIC(trans.get("Shape.Parabolicseries"),
                                ////A parabolic series nose cone has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
                                trans.get("Shape.Parabolicseries.desc1"),
                                ////A parabolic series transition has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
                                trans.get("Shape.Parabolicseries.desc2")) {
-                       
+
                        // In principle a parabolic transition is clippable, but the difference is
                        // negligible.
-                       
+
                        @Override
                        public boolean usesParameter() { // Range 0...1
                                return true;
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 1.0;
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -668,28 +745,28 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 1;
-                               
+
                                return radius * ((2 * x / length - param * pow2(x / length)) / (2 - param));
                        }
                },
-               
+
                //// Haack series
                HAACK(trans.get("Shape.Haackseries"),
                                //// The Haack series nose cones are designed to minimize drag.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
                                trans.get("Shape.Haackseries.desc1"),
-                               //// The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.            
+                               //// The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
                                trans.get("Shape.Haackseries.desc2"), true) {
-                       
+
                        @Override
                        public boolean usesParameter() {
                                return true;
                        }
-                       
+
                        @Override
                        public double maxParameter() {
                                return 1.0 / 3.0; // Range 0...1/3
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -697,7 +774,7 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 2;
-                               
+
                                double theta = Math.acos(1 - 2 * x / length);
                                if (MathUtil.equals(param, 0)) {
                                        return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
@@ -705,7 +782,7 @@ public class Transition extends SymmetricComponent {
                                return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
                        }
                },
-               
+
                //              POLYNOMIAL("Smooth polynomial",
                //                              "A polynomial is fitted such that the nose cone profile is horizontal "+
                //                              "at the aft end of the transition.  The angle at the tip is defined by "+
@@ -737,18 +814,18 @@ public class Transition extends SymmetricComponent {
                //                      }
                //              }
                ;
-               
+
                // Privete fields of the shapes
                private final String name;
                private final String transitionDesc;
                private final String noseconeDesc;
                private final boolean canClip;
-               
+
                // Non-clippable constructor
                Shape(String name, String noseconeDesc, String transitionDesc) {
                        this(name, noseconeDesc, transitionDesc, false);
                }
-               
+
                // Clippable constructor
                Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) {
                        this.name = name;
@@ -756,29 +833,29 @@ public class Transition extends SymmetricComponent {
                        this.noseconeDesc = noseconeDesc;
                        this.transitionDesc = transitionDesc;
                }
-               
-               
+
+
                /**
                 * Return the name of the transition shape name.
                 */
                public String getName() {
                        return name;
                }
-               
+
                /**
                 * Get a description of the Transition shape.
                 */
                public String getTransitionDescription() {
                        return transitionDesc;
                }
-               
+
                /**
                 * Get a description of the NoseCone shape.
                 */
                public String getNoseConeDescription() {
                        return noseconeDesc;
                }
-               
+
                /**
                 * Check whether the shape differs in clipped mode.  The clipping should be
                 * enabled by default if possible.
@@ -786,35 +863,35 @@ public class Transition extends SymmetricComponent {
                public boolean isClippable() {
                        return canClip;
                }
-               
+
                /**
                 * Return whether the shape uses the shape parameter.  (Default false.)
                 */
                public boolean usesParameter() {
                        return false;
                }
-               
+
                /**
                 * Return the minimum value of the shape parameter.  (Default 0.)
                 */
                public double minParameter() {
                        return 0.0;
                }
-               
+
                /**
                 * Return the maximum value of the shape parameter.  (Default 1.)
                 */
                public double maxParameter() {
                        return 1.0;
                }
-               
+
                /**
                 * Return the default value of the shape parameter.  (Default 0.)
                 */
                public double defaultParameter() {
                        return 0.0;
                }
-               
+
                /**
                 * Calculate the basic radius of a transition with the given radius, length and
                 * shape parameter at the point x from the tip of the component.  It is assumed
@@ -828,8 +905,8 @@ public class Transition extends SymmetricComponent {
                 * @return       The basic radius at the given position.
                 */
                public abstract double getRadius(double x, double radius, double length, double param);
-               
-               
+
+
                /**
                 * Returns the name of the shape (same as getName()).
                 */
@@ -837,5 +914,22 @@ public class Transition extends SymmetricComponent {
                public String toString() {
                        return name;
                }
+
+        /**
+         * Lookup the Shape given the localized name.  This differs from the standard valueOf as that looks up
+         * based on the canonical name, not the localized name which is an instance var.
+         *
+         * @param localizedName
+         * @return
+         */
+        public static Shape toShape(String localizedName) {
+            Shape[] values = Shape.values();
+            for (Shape value : values) {
+                if (value.getName().equals(localizedName)) {
+                    return value;
+                }
+            }
+            return null;
+        }
        }
 }