diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index 22b96b4eb..5ac6cbf3d 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -228,6 +228,10 @@ edtmotorconfdlg.tbl.Separationheader = Separation
 RenameConfigDialog.title = Rename Configuration
 RenameConfigDialog.lbl.name = Name for flight configuration:
 RenameConfigDialog.but.reset = Reset to default
+RenameConfigDialog.lbl.infoMotors = The text '<b>{motors}</b>' will be replaced with the <b>motor designation(s).</b><br><pre>\te.g. '{motors} \u2192 'M1350-0'</pre>
+RenameConfigDialog.lbl.infoManufacturers = The text '<b>{manufacturers}</b>' will be replaced with the <b>motor manufacturer(s).</b><br><pre>\te.g. '{manufacturers}' \u2192 'AeroTech'</pre>
+RenameConfigDialog.lbl.infoCombination = A <b>combination</b> of the above can be used.<br><pre>\te.g. '{manufacturers motors}' \u2192 'AeroTech M1350-0'</pre>
+
 
 ! Example design dialog
 exdesigndlg.but.open = Open
@@ -551,6 +555,7 @@ simpanel.col.Timetoapogee = Time to apogee
 simpanel.col.Flighttime = Flight time
 simpanel.col.Groundhitvelocity = Ground hit velocity
 simpanel.ttip.uptodate = <i>Up to date</i>
+simpanel.ttip.loaded = <i>Loaded from file</i>
 simpanel.ttip.outdated = <i><font color=\"red\">Out of date</font></i><br>Click <i><b>Run simulations</b></i> to simulate.
 simpanel.ttip.external = <i>Imported data</i>
 simpanel.ttip.notSimulated = <i>Not simulated yet</i><br>Click <i><b>Run simulations</b></i> to simulate.
@@ -2261,6 +2266,7 @@ SeparationSelectionDialog.opt.override = Override for the {0} flight configurati
 MotorConfigurationPanel.description = <b>Select the motors and motor ignition events of the selected flight configuration.</b><br> <em>Motor mounts:</em> Select which components function as motor mounts.<br> <em>Motor configurations:</em> Select the motor and ignition event for each motor mount.
 
 MotorDescriptionSubstitutor.description = Motors in the configuration
+MotorManufacturerSubstitutor.description = Motor manufacturers in the configuration
 
 
 !Photo Panel
diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
index bc75f02a6..63ede7ad9 100644
--- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
+++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
@@ -380,6 +380,20 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
 								}
 								
 							}
+						} else {
+							/*
+							It could be that the component is a child of a PodSet or ParallelStage, and it is flush with
+							the previous component.  In this case, the component is overlapping.
+							 */
+							RocketComponent prevCompParent = prevComp.getParent();
+							RocketComponent compParent = comp.getParent();
+							int prevCompPos = prevCompParent.getChildPosition(prevComp);
+							RocketComponent nextComp = prevCompPos + 1 >= prevCompParent.getChildCount() ?
+									null : prevCompParent.getChild(prevCompPos + 1);
+							if ((compParent instanceof PodSet || compParent instanceof ParallelStage) &&
+									MathUtil.equals(symXfore, prevXaft) && (compParent.getParent() == nextComp)) {
+								warnings.add(Warning.PODSET_OVERLAP, comp.getParent().toString());
+							}
 						}
 					}
 					prevComp = sym;
diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java
index a695a1165..75f6cf0b2 100644
--- a/core/src/net/sf/openrocket/document/Simulation.java
+++ b/core/src/net/sf/openrocket/document/Simulation.java
@@ -146,40 +146,22 @@ public class Simulation implements ChangeSource, Cloneable {
 		if (options == null)
 			throw new IllegalArgumentException("options cannot be null");
 
-		this.document = document;
 		this.rocket = rocket;
-
-		if (status == Status.UPTODATE) {
-			this.status = Status.LOADED;
-		} else if (data == null) {
-			this.status = Status.NOT_SIMULATED;
-		} else {
-			this.status = status;
-		}
-		
 		this.name = name;
-		
+		this.status = status;
+		this.simulatedConditions = options.clone();
+		this.simulatedData = data;
+		this.document = document;
+		addChangeListener(this.document);
+			
 		this.options = options;
-
+		this.options.addChangeListener(new ConditionListener());
+		
 		final FlightConfiguration config = rocket.getSelectedConfiguration();
 		this.setFlightConfigurationId(config.getFlightConfigurationID());
-				
-		options.addChangeListener(new ConditionListener());
-		addChangeListener(document);
-		
-		if (extensions != null) {
-			this.simulationExtensions.addAll(extensions);
-		}
-		
-		
-		if (data != null && this.status != Status.NOT_SIMULATED) {
-			simulatedData = data;
-			if (this.status == Status.LOADED) {
-				simulatedConditions = options.clone();
-				simulatedConfigurationID = config.getModID();
-			}
-		}
-		
+		this.simulatedConfigurationID = config.getModID();
+
+		this.simulationExtensions.addAll(extensions);
 	}
 
 	public FlightConfiguration getActiveConfiguration() {
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
index 6d90c7050..99d819327 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
@@ -92,12 +92,6 @@ class FlightDataHandler extends AbstractElementHandler {
 	public void endHandler(String element, HashMap<String, String> attributes,
 			String content, WarningSet warnings) {
 
-		// If no <databranch> tag in XML, then there is no sim data
-		if (dataHandler == null) {
-			data = null;
-			return;
-		}
-
 		if (branches.size() > 0) {
 			data = new FlightData(branches.toArray(new FlightDataBranch[0]));
 		} else {
@@ -158,4 +152,4 @@ class FlightDataHandler extends AbstractElementHandler {
 	}
 	
 	
-}
\ No newline at end of file
+}
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
index 6483e09bc..629d3be25 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
@@ -109,13 +109,6 @@ class SingleSimulationHandler extends AbstractElementHandler {
 	public void endHandler(String element, HashMap<String, String> attributes,
 			String content, WarningSet warnings) {
 		
-		String s = attributes.get("status");
-		Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
-		if (status == null) {
-			warnings.add("Simulation status unknown, assuming outdated.");
-			status = Simulation.Status.OUTDATED;
-		}
-		
 		SimulationOptions options;
 		FlightConfigurationId idToSet= FlightConfigurationId.ERROR_FCID;
 		if (conditionHandler != null) {
@@ -126,16 +119,23 @@ class SingleSimulationHandler extends AbstractElementHandler {
 			options = new SimulationOptions();
 		}
 		
-		if (name == null)
+		if (name == null) 
 			name = "Simulation";
 		
+		// If the simulation was saved with flight data (which may just be a summary)
+		// mark it as loaded from the file else as not simulated.  We're ignoring the
+		// simulation status attribute, since (1) it really isn't relevant now, and (2)
+		// sim summaries are getting marked as not simulated when they're saved
 		FlightData data;
 		if (dataHandler == null)
 			data = null;
 		else
 			data = dataHandler.getFlightData();
 
-		if (data == null) {
+		Simulation.Status status;
+		if (data != null) {
+			status = Status.LOADED;
+		} else {
 			status = Status.NOT_SIMULATED;
 		}
 		
@@ -153,4 +153,4 @@ class SingleSimulationHandler extends AbstractElementHandler {
 		return extension;
 	}
 	
-}
\ No newline at end of file
+}
diff --git a/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java
new file mode 100644
index 000000000..9c534d079
--- /dev/null
+++ b/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java
@@ -0,0 +1,250 @@
+package net.sf.openrocket.formatting;
+
+import com.google.inject.Inject;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorConfiguration;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.plugin.Plugin;
+import net.sf.openrocket.rocketcomponent.AxialStage;
+import net.sf.openrocket.rocketcomponent.FlightConfiguration;
+import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.Chars;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * General substitutor for motor configurations. This currently includes substitutions for
+ *  - {motors} - the motor designation (e.g. "M1350-0")
+ *  - {manufacturers} - the motor manufacturer (e.g. "AeroTech")
+ *  - a combination of motors and manufacturers, e.g. {motors | manufacturers} -> "M1350-0 | AeroTech"
+ *      You can choose which comes first and what the separator is. E.g. {manufacturers, motors} -> "AeroTech, M1350-0".
+ *
+ * <p>
+ * This substitutor is added through injection. All substitutors with the "@Plugin" tag in the formatting package will
+ * be included automatically.
+ */
+@Plugin
+public class MotorConfigurationSubstitutor implements RocketSubstitutor {
+    public static final String SUBSTITUTION_START = "{";
+    public static final String SUBSTITUTION_END = "}";
+    public static final String SUBSTITUTION_MOTORS = "motors";
+    public static final String SUBSTITUTION_MANUFACTURERS = "manufacturers";
+
+    // Substitutions for combinations of motors and manufacturers
+    private static final String SUBSTITUTION_PATTERN = "\\" + SUBSTITUTION_START +
+            "(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" +
+            "(.*?)" +
+            "(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" +
+            "\\" + SUBSTITUTION_END;
+
+    @Inject
+    private Translator trans;
+
+    @Override
+    public boolean containsSubstitution(String input) {
+        return getSubstitutionContent(input) != null;
+    }
+
+    @Override
+    public String substitute(String input, Rocket rocket, FlightConfigurationId configId) {
+        String description = getConfigurationSubstitution(input, rocket, configId);
+        String substitutionString = getSubstiutionString(input);
+        if (substitutionString != null) {
+            return input.replace(substitutionString, description);
+        }
+        return input;
+    }
+
+    @Override
+    public Map<String, String> getDescriptions() {
+        return null;
+    }
+
+    public String getConfigurationSubstitution(String input, Rocket rocket, FlightConfigurationId fcid) {
+        StringBuilder configurations = new StringBuilder();
+        int motorCount = 0;
+
+        // Iterate over each stage and store the manufacturer of each motor
+        List<List<String>> list = new ArrayList<>();
+        List<String> currentList = new ArrayList<>();
+
+        String[] content = getSubstitutionContent(input);
+        if (content == null) {
+            return "";
+        }
+
+        FlightConfiguration config = rocket.getFlightConfiguration(fcid);
+        for (RocketComponent c : rocket) {
+            if (c instanceof AxialStage) {
+                currentList = new ArrayList<>();
+                list.add(currentList);
+            } else if (c instanceof MotorMount) {
+                MotorMount mount = (MotorMount) c;
+                MotorConfiguration inst = mount.getMotorConfig(fcid);
+                Motor motor = inst.getMotor();
+
+                if (mount.isMotorMount() && config.isComponentActive(mount) && (motor != null)) {
+                    String motorDesignation = motor.getDesignation(inst.getEjectionDelay());
+                    String manufacturer = "";
+                    if (motor instanceof ThrustCurveMotor) {
+                        manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName();
+                    }
+
+                    for (int i = 0; i < mount.getMotorCount(); i++) {
+                        if (content.length == 2) {
+                            if (SUBSTITUTION_MOTORS.equals(content[1])) {
+                                currentList.add(motorDesignation);
+                            } else if (SUBSTITUTION_MANUFACTURERS.equals(content[1])) {
+                                currentList.add(manufacturer);
+                            } else {
+                                continue;
+                            }
+                        } else if (content.length == 4) {
+                            String configString;
+                            if (content[1].equals(SUBSTITUTION_MOTORS)) {
+                                configString = motorDesignation;
+                            } else if (content[1].equals(SUBSTITUTION_MANUFACTURERS)) {
+                                configString = manufacturer;
+                            } else {
+                                continue;
+                            }
+                            configString += content[2];
+                            if (content[3].equals(SUBSTITUTION_MOTORS)) {
+                                configString += motorDesignation;
+                            } else if (content[3].equals(SUBSTITUTION_MANUFACTURERS)) {
+                                configString += manufacturer;
+                            } else {
+                                continue;
+                            }
+                            currentList.add(configString);
+                        } else {
+                            continue;
+                        }
+                        motorCount++;
+                    }
+                }
+            }
+        }
+
+        if (motorCount == 0) {
+            return trans.get("Rocket.motorCount.Nomotor");
+        }
+
+        // Change multiple occurrences of a motor to n x motor
+        List<String> stages = new ArrayList<>();
+        for (List<String> stage : list) {
+            String stageName = "";
+            String previous = null;
+            int count = 0;
+
+            Collections.sort(stage);
+            for (String current : stage) {
+                if (current.equals(previous)) {
+                    count++;
+                } else {
+                    if (previous != null) {
+                        String s = count > 1 ? count + Chars.TIMES + previous : previous;
+                        stageName = stageName.equals("") ? s : stageName + "," + s;
+                    }
+
+                    previous = current;
+                    count = 1;
+                }
+            }
+
+            if (previous != null) {
+                String s = count > 1 ? "" + count + Chars.TIMES + previous : previous;
+                stageName = stageName.equals("") ? s : stageName + "," + s;
+            }
+
+            stages.add(stageName);
+        }
+
+        for (int i = 0; i < stages.size(); i++) {
+            String s = stages.get(i);
+            if (s.equals("") && config.isStageActive(i)) {
+                s = trans.get("Rocket.motorCount.noStageMotors");
+            }
+
+            configurations.append(i == 0 ? s : "; " + s);
+        }
+
+        return configurations.toString();
+    }
+
+    /**
+     * Returns which string in input should be replaced, or null if no text needs to be replaced.
+     * @param input The input string
+     * @return The string to replace, or null if no text needs to be replaced.
+     */
+    private static String getSubstiutionString(String input) {
+        String[] content = getSubstitutionContent(input);
+        if (content != null) {
+            return content[0];
+        }
+        return null;
+    }
+
+    /**
+     * Fills in the content of the substitution tag and the separator.
+     * If there are both a motor and a manufacturer substitution tag, the array will contain the following:
+     *  [0] = The full tag, including substitution start and end
+     *  [1] = The motor/manufacturer substitution tag, depending on which one was found first.
+     *  if there are two substitution tags, the array will also contain the following:
+     *  ([2] = The separator)
+     *  ([3] = The motor/manufacturer substitution tag, depending on which one was found first.)
+     * @param input The input string
+     * @return The content of the substitution tag and the separator, or null if no text needs to be replaced.
+     */
+    private static String[] getSubstitutionContent(String input) {
+        // First try with only the motors tag
+        String pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MOTORS + ")" + "\\" + SUBSTITUTION_END;
+        Pattern regexPattern = Pattern.compile(pattern);
+        Matcher matcher = regexPattern.matcher(input);
+        if (matcher.find()) {
+            String[] content = new String[2];
+            content[0] = matcher.group(0);
+            content[1] = matcher.group(1);
+            return content;
+        }
+        // First try with only the manufacturers tag
+        pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MANUFACTURERS + ")" + "\\" + SUBSTITUTION_END;
+        regexPattern = Pattern.compile(pattern);
+        matcher = regexPattern.matcher(input);
+        if (matcher.find()) {
+            String[] content = new String[2];
+            content[0] = matcher.group(0);
+            content[1] = matcher.group(1);
+            return content;
+        }
+
+        // Then try combined patterns
+        pattern = SUBSTITUTION_PATTERN;
+        regexPattern = Pattern.compile(pattern);
+        matcher = regexPattern.matcher(input);
+        if (matcher.find()) {
+            String[] content = new String[4];
+            content[0] = matcher.group(0);
+            content[1] = matcher.group(1);
+            if (matcher.groupCount() >= 3) {
+                content[2] = matcher.group(2);
+                content[3] = matcher.group(3);
+                for (int i = 4; i < matcher.groupCount(); i++) {
+                    content[3] += matcher.group(i);
+                }
+            }
+            return content;
+        }
+        return null;
+    }
+}
+
diff --git a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java
deleted file mode 100644
index f02f82540..000000000
--- a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package net.sf.openrocket.formatting;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import com.google.inject.Inject;
-
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.motor.MotorConfiguration;
-import net.sf.openrocket.plugin.Plugin;
-import net.sf.openrocket.rocketcomponent.AxialStage;
-import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.util.ArrayList;
-import net.sf.openrocket.util.Chars;
-
-@Plugin
-public class MotorDescriptionSubstitutor implements RocketSubstitutor {
-	public static final String SUBSTITUTION = "{motors}";
-	
-	@Inject
-	private Translator trans;
-	
-	@Override
-	public boolean containsSubstitution(String str) {
-		return str.contains(SUBSTITUTION);
-	}
-	
-	@Override
-	public String substitute(String str, Rocket rocket, FlightConfigurationId configId) {
-		String description = getMotorConfigurationDescription(rocket, configId);
-		return str.replace(SUBSTITUTION, description);
-	}
-	
-	@Override
-	public Map<String, String> getDescriptions() {
-		Map<String, String> desc = new HashMap<String, String>();
-		desc.put(SUBSTITUTION, trans.get("MotorDescriptionSubstitutor.description"));
-		return null;
-	}
-	
-	
-	
-	public String getMotorConfigurationDescription(Rocket rocket, FlightConfigurationId fcid) {
-		String name;
-		int motorCount = 0;
-		
-		// Generate the description
-		
-		// First iterate over each stage and store the designations of each motor
-		List<List<String>> list = new ArrayList<List<String>>();
-		List<String> currentList = Collections.emptyList();
-		
-		Iterator<RocketComponent> iterator = rocket.iterator();
-		while (iterator.hasNext()) {
-			RocketComponent c = iterator.next();
-			
-			if (c instanceof AxialStage) {
-				
-				currentList = new ArrayList<String>();
-				list.add(currentList);
-				
-			} else if (c instanceof MotorMount) {
-				
-				MotorMount mount = (MotorMount) c;
-				MotorConfiguration inst = mount.getMotorConfig(fcid);
-				Motor motor = inst.getMotor();
-				
-				if (mount.isMotorMount() && motor != null) {
-					String designation = motor.getDesignation(inst.getEjectionDelay());
-					
-					for (int i = 0; i < mount.getMotorCount(); i++) {
-						currentList.add(designation);
-						motorCount++;
-					}
-				}
-				
-			}
-		}
-		
-		if (motorCount == 0) {
-			return trans.get("Rocket.motorCount.Nomotor");
-		}
-		
-		// Change multiple occurrences of a motor to n x motor
-		List<String> stages = new ArrayList<String>();
-		
-		for (List<String> stage : list) {
-			String stageName = "";
-			String previous = null;
-			int count = 0;
-			
-			Collections.sort(stage);
-			for (String current : stage) {
-				if (current.equals(previous)) {
-					
-					count++;
-					
-				} else {
-					
-					if (previous != null) {
-						String s = "";
-						if (count > 1) {
-							s = "" + count + Chars.TIMES + previous;
-						} else {
-							s = previous;
-						}
-						
-						if (stageName.equals(""))
-							stageName = s;
-						else
-							stageName = stageName + "," + s;
-					}
-					
-					previous = current;
-					count = 1;
-					
-				}
-			}
-			if (previous != null) {
-				String s = "";
-				if (count > 1) {
-					s = "" + count + Chars.TIMES + previous;
-				} else {
-					s = previous;
-				}
-				
-				if (stageName.equals(""))
-					stageName = s;
-				else
-					stageName = stageName + "," + s;
-			}
-			
-			stages.add(stageName);
-		}
-		
-		name = "";
-		for (int i = 0; i < stages.size(); i++) {
-			String s = stages.get(i);
-			if (s.equals(""))
-				s = trans.get("Rocket.motorCount.noStageMotors");
-			if (i == 0)
-				name = name + s;
-			else
-				name = name + "; " + s;
-		}
-		return name;
-	}
-	
-	
-	
-}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java
index 889d0f6d7..1b2ec0f0d 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java
@@ -8,6 +8,7 @@ import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
+import net.sf.openrocket.formatting.RocketDescriptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,6 +37,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 
     private String configurationName;
 	public static String DEFAULT_CONFIG_NAME = "[{motors}]";
+	private final RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
 	
 	protected final Rocket rocket;
 	protected final FlightConfigurationId fcid;
@@ -566,9 +568,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 	 */
 	public String getName() {
 		if (configurationName == null) {
-			return getOneLineMotorDescription();
+			configurationName = DEFAULT_CONFIG_NAME;
 		}
-		return configurationName.replace(DEFAULT_CONFIG_NAME, getOneLineMotorDescription());
+		return descriptor.format(configurationName, rocket, fcid);
 	}
 
 	/**
@@ -582,34 +584,6 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 		}
 		return configurationName;
 	}
-	
-	private String getOneLineMotorDescription(){
-		StringBuilder buff = new StringBuilder("[");
-		boolean first = true;
-		int activeMotorCount = 0;
-		for ( RocketComponent comp : getActiveComponents() ){
-			if (( comp instanceof MotorMount )&&( ((MotorMount)comp).isMotorMount())){ 
-				MotorMount mount = (MotorMount)comp;
-				MotorConfiguration motorConfig = mount.getMotorConfig( fcid);
-				
-				if( first ){
-					first = false;
-				}else{
-					buff.append(";");
-				}
-				
-				if( ! motorConfig.isEmpty()){
-					buff.append(motorConfig.toMotorCommonName());
-					++activeMotorCount;
-				}
-			}
-		}
-		if( 0 == activeMotorCount ){
-			return trans.get("noMotors");
-		}
-		buff.append("]");
-		return buff.toString();
-	}
 
 	@Override
 	public String toString() { return this.getName(); }
@@ -932,7 +906,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 	}
 	
 	public String toDebug() {
-		return this.fcid.toDebug()+" (#"+configurationInstanceId+") "+ getOneLineMotorDescription();
+		return this.fcid.toDebug()+" (#"+configurationInstanceId+") "+ getName();
 	}
 	
 	// DEBUG / DEVEL
diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
index cb2568d59..ec82b844c 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
@@ -1296,6 +1296,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 		mutex.verify();
 		return 0;
 	}
+
+	public double getRadiusOffset(RadiusMethod method) {
+		double radius = getRadiusMethod().getRadius(parent, this, getRadiusOffset());
+		return method.getAsOffset(parent, this, radius);
+	}
 	
 	public RadiusMethod getRadiusMethod() {
 		return RadiusMethod.COAXIAL;
@@ -2059,20 +2064,37 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	}
 
 	/**
-	 * Return all the component assemblies that are a child of this component
-	 * @return list of ComponentAssembly components that are a child of this component
+	 * Return all the component assemblies that are a direct/indirect child of this component
+	 * @return list of ComponentAssembly components that are a direct/indirect child of this component
 	 */
-	public final List<RocketComponent> getChildAssemblies() {
+	public final List<ComponentAssembly> getAllChildAssemblies() {
 		checkState();
 
 		Iterator<RocketComponent> children = iterator(false);
 
-		List<RocketComponent> result = new ArrayList<>();
+		List<ComponentAssembly> result = new ArrayList<>();
 
 		while (children.hasNext()) {
 			RocketComponent child = children.next();
 			if (child instanceof ComponentAssembly) {
-				result.add(child);
+				result.add((ComponentAssembly) child);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Return all the component assemblies that are a direct child of this component
+	 * @return list of ComponentAssembly components that are a direct child of this component
+	 */
+	public final List<ComponentAssembly> getDirectChildAssemblies() {
+		checkState();
+
+		List<ComponentAssembly> result = new ArrayList<>();
+
+		for (RocketComponent child : this.getChildren()) {
+			if (child instanceof ComponentAssembly) {
+				result.add((ComponentAssembly) child);
 			}
 		}
 		return result;
diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
index b4204ff85..9f1afe722 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
@@ -7,10 +7,10 @@ import java.util.Collection;
 import java.util.List;
 
 import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.rocketcomponent.position.AxialMethod;
 import net.sf.openrocket.util.BoundingBox;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
 
 /**
  * Class for an axially symmetric rocket component generated by rotating
@@ -622,7 +622,8 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
 				while (0 <= searchSiblingIndex) {
 					final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex);
 					if ((searchSibling instanceof SymmetricComponent) && inline(searchSibling)) {
-						return (SymmetricComponent) searchSibling;
+						return getPreviousSymmetricComponentFromComponentAssembly((SymmetricComponent) searchSibling,
+								(SymmetricComponent) searchSibling, 0);
 					}
 					--searchSiblingIndex;
 				}
@@ -636,14 +637,81 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
 			}
 		}
 
-		// one last thing -- I could be the child of a PodSet, and in line with
+		// one last thing -- I could be the child of a ComponentAssembly, and in line with
 		// the SymmetricComponent that is my grandParent
 		if ((grandParent instanceof SymmetricComponent) && inline(grandParent)) {
-			return (SymmetricComponent) grandParent;
+			// If the grandparent is actually before me, then this is the previous component
+			if ((parent.getAxialOffset(AxialMethod.TOP) + getAxialOffset(AxialMethod.TOP)) > 0) {
+				return (SymmetricComponent) grandParent;
+			}
+			// If not, then search for the component before the grandparent
+			else {
+				// NOTE: will be incorrect if the ComponentAssembly is even further to the front than the
+				// previous component of the grandparent. But that would be really bad rocket design...
+				return ((SymmetricComponent) grandParent).getPreviousSymmetricComponent();
+			}
 		}
 
 		return null;
 	}
+
+	/**
+	 * Checks if parent has component assemblies that have potential previous components.
+	 * A child symmetric component of a component assembly is a potential previous component if:
+	 * 	- it is inline with the parent
+	 * 	- it is flush with the end of the parent
+	 * 	- it is larger in aft radius than the parent
+	 * @param parent parent component to check for child component assemblies
+	 * @param previous the current previous component candidate
+	 * @param flushOffset an extra offset to be added to check for flushness. This is used when recursively running this
+	 *                    method to check children of children of the original parent are flush with the end of the
+	 *                    original parent.
+	 * @return the previous component if it is found
+	 */
+	private SymmetricComponent getPreviousSymmetricComponentFromComponentAssembly(SymmetricComponent parent,
+																				  SymmetricComponent previous, double flushOffset) {
+		if (previous == null) {
+			return parent;
+		}
+		if (parent == null) {
+			return previous;
+		}
+
+		double maxRadius = previous.isAftRadiusAutomatic() ? 0 : previous.getAftRadius();
+		SymmetricComponent previousComponent = previous;
+		for (ComponentAssembly assembly : parent.getDirectChildAssemblies()) {
+			if (assembly.getChildCount() == 0) {
+				continue;
+			}
+			/*
+			 * Check if the component assembly's last child is a symmetric component that is:
+			 *	- inline with the parent
+			 * 	- flush with the end of the parent
+			 * 	- larger in aft radius than the parent
+			 * in that case, this component assembly is the new previousComponent.
+			 */
+			RocketComponent lastChild = assembly.getChild(assembly.getChildCount() - 1);
+			if (!( (lastChild instanceof SymmetricComponent) && parent.inline(lastChild) )) {
+				continue;
+			}
+			SymmetricComponent lastSymmetricChild = (SymmetricComponent) lastChild;
+			double flushDeviation = flushOffset + assembly.getAxialOffset(AxialMethod.BOTTOM);		// How much the last child is flush with the parent
+
+			// If the last symmetric child from the assembly if flush with the end of the parent and larger than the
+			// current previous component, then this is the new previous component
+			if (MathUtil.equals(flushDeviation, 0) && !lastSymmetricChild.isAftRadiusAutomatic() &&
+					lastSymmetricChild.getAftRadius() > maxRadius) {
+				previousComponent = lastSymmetricChild;
+				maxRadius = previousComponent.getAftRadius();
+			}
+			// It could be that there is a child component assembly that is flush with the end of the parent or larger
+			// Recursively check assembly's children
+			previousComponent = getPreviousSymmetricComponentFromComponentAssembly(lastSymmetricChild, previousComponent, flushDeviation);
+			maxRadius = previousComponent != null ? previousComponent.getAftRadius() : maxRadius;
+		}
+
+		return previousComponent;
+	}
 	
 	/**
 	 * Return the next symmetric component, or null if none exists.
@@ -670,7 +738,8 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
 				while (searchSiblingIndex < searchParent.getChildCount()) {
 					final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex);
 					if ((searchSibling instanceof SymmetricComponent) && inline(searchSibling)) {
-						return (SymmetricComponent) searchSibling;
+						return getNextSymmetricComponentFromComponentAssembly((SymmetricComponent) searchSibling,
+								(SymmetricComponent) searchSibling, 0);
 					}
 					++searchSiblingIndex;
 				}
@@ -681,13 +750,22 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
 			searchSiblingIndex = 0;
 		}
 
-		// One last thing -- I could have a child that is a PodSet that is in line
+		// One last thing -- I could have a child that is a ComponentAssembly that is in line
 		// with me
 		for (RocketComponent child : getChildren()) {
-			if (child instanceof PodSet) {
+			if (child instanceof ComponentAssembly) {
 				for (RocketComponent grandchild : child.getChildren()) {
 					if ((grandchild instanceof SymmetricComponent) && inline(grandchild)) {
-						return (SymmetricComponent) grandchild;
+						// If the grandparent is actually after me, then this is the next component
+						if ((parent.getAxialOffset(AxialMethod.BOTTOM) + getAxialOffset(AxialMethod.BOTTOM)) < 0) {
+							return (SymmetricComponent) grandchild;
+						}
+						// If not, then search for the component after the grandparent
+						else {
+							// NOTE: will be incorrect if the ComponentAssembly is even further to the back than the
+							// next component of the grandparent. But that would be really bad rocket design...
+							return ((SymmetricComponent) grandchild).getNextSymmetricComponent();
+						}
 					}
 				}
 			}
@@ -696,6 +774,64 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
 		return null;
 	}
 
+	/**
+	 * Checks if parent has component assemblies that have potential next components.
+	 * A child symmetric component of a component assembly is a potential next component if:
+	 * 	- it is inline with the parent
+	 * 	- it is flush with the front of the parent
+	 * 	- it is larger in fore radius than the parent
+	 * @param parent parent component to check for child component assemblies
+	 * @param next the next previous component candidate
+	 * @param flushOffset an extra offset to be added to check for flushness. This is used when recursively running this
+	 *                    method to check children of children of the original parent are flush with the front of the
+	 *                    original parent.
+	 * @return the next component if it is found
+	 */
+	private SymmetricComponent getNextSymmetricComponentFromComponentAssembly(SymmetricComponent parent,
+																				  SymmetricComponent next, double flushOffset) {
+		if (next == null) {
+			return parent;
+		}
+		if (parent == null) {
+			return next;
+		}
+
+		double maxRadius = next.isForeRadiusAutomatic() ? 0 : next.getForeRadius();
+		SymmetricComponent nextComponent = next;
+		for (ComponentAssembly assembly : parent.getDirectChildAssemblies()) {
+			if (assembly.getChildCount() == 0) {
+				continue;
+			}
+			/*
+			 * Check if the component assembly's last child is a symmetric component that is:
+			 *	- inline with the parent
+			 * 	- flush with the front of the parent
+			 * 	- larger in fore radius than the parent
+			 * in that case, this component assembly is the new nextComponent.
+			 */
+			RocketComponent firstChild = assembly.getChild(0);
+			if (!( (firstChild instanceof SymmetricComponent) && parent.inline(firstChild) )) {
+				continue;
+			}
+			SymmetricComponent firstSymmetricChild = (SymmetricComponent) firstChild;
+			double flushDeviation = flushOffset + assembly.getAxialOffset(AxialMethod.TOP);		// How much the last child is flush with the parent
+
+			// If the first symmetric child from the assembly if flush with the front of the parent and larger than the
+			// current next component, then this is the new next component
+			if (MathUtil.equals(flushDeviation, 0) && !firstSymmetricChild.isForeRadiusAutomatic() &&
+					firstSymmetricChild.getForeRadius() > maxRadius) {
+				nextComponent = firstSymmetricChild;
+				maxRadius = nextComponent.getForeRadius();
+			}
+			// It could be that there is a child component assembly that is flush with the front of the parent or larger
+			// Recursively check assembly's children
+			nextComponent = getNextSymmetricComponentFromComponentAssembly(firstSymmetricChild, nextComponent, flushDeviation);
+			maxRadius = nextComponent != null ? nextComponent.getForeRadius() : maxRadius;
+		}
+
+		return nextComponent;
+	}
+
 	/***
 	 * Determine whether a candidate symmetric component is in line with us
 	 *
diff --git a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java
index 5b466f0d8..f2573bdeb 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java
@@ -10,7 +10,7 @@ public enum RadiusMethod implements DistanceMethod {
 	// just as a reminder:
 	// public T[] getEnumConstants()
 
-	// both components are on the same axis
+	// Same axis as the target component
 	COAXIAL ( Application.getTranslator().get("RocketComponent.Position.Method.Radius.COAXIAL") ){
 		@Override
 		public double getRadius( final RocketComponent parentComponent, final RocketComponent thisComponent, final double requestedOffset ){
@@ -22,7 +22,8 @@ public enum RadiusMethod implements DistanceMethod {
 			return 0;
 		}
 	},
-	
+
+	// Center of the parent component
 	FREE(Application.getTranslator().get("RocketComponent.Position.Method.Radius.FREE") ){
 		@Override
 		public boolean clampToZero() { return false; }
@@ -37,7 +38,8 @@ public enum RadiusMethod implements DistanceMethod {
 			return radius;
 		}
 	},
-	
+
+	// Surface of the parent component
 	RELATIVE ( Application.getTranslator().get("RocketComponent.Position.Method.Radius.RELATIVE") ){
 		@Override
 		public boolean clampToZero() { return false; }
diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
index 9293773bc..f51b9ee99 100644
--- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
+++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
@@ -469,7 +469,6 @@ public class BarrowmanCalculatorTest {
 		final FlightConfiguration testConfig = testRocket.getSelectedConfiguration();
 		final FlightConditions testConditions = new FlightConditions(testConfig);
 		
-		TestRockets.dumpRocket(testRocket, "/home/joseph/rockets/openrocket/git/openrocket/work/testrocket.ork");
 		final BarrowmanCalculator testCalc = new BarrowmanCalculator();
 		double testCP = testCalc.getCP(testConfig, testConditions, warnings).x;
 		final AerodynamicForces testForces = testCalc.getAerodynamicForces(testConfig, testConditions, warnings);
@@ -483,13 +482,19 @@ public class BarrowmanCalculatorTest {
 		// move the pod back.
 		pod.setAxialOffset(pod.getAxialOffset() + 0.1);
 		testCP = testCalc.getCP(testConfig, testConditions, warnings).x;
-		assertFalse("should be warning from gap in airframe", warnings.isEmpty());
+		assertEquals("should be warning from gap in airframe", 1, warnings.size());
 
 		// move the pod forward.
 		warnings.clear();
-		pod.setAxialOffset(pod.getAxialOffset() - 0.2);
-		testCP = testCalc.getCP(testConfig, testConditions, warnings).x;		
-		assertFalse("should be warning from airframe overlap", warnings.isEmpty());
+		pod.setAxialOffset(pod.getAxialOffset() - 0.3);
+		testCP = testCalc.getCP(testConfig, testConditions, warnings).x;
+		assertEquals("should be warning from airframe overlap", 1, warnings.size());
+
+		// move the pod back.
+		warnings.clear();
+		pod.setAxialOffset(pod.getAxialOffset() + 0.1);
+		testCP = testCalc.getCP(testConfig, testConditions, warnings).x;
+		assertEquals("should be warning from podset airframe overlap", 1, warnings.size());
 	}
 		
 }
diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java
index b671af3b3..372d3f2de 100644
--- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java
+++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java
@@ -580,10 +580,114 @@ public class FlightConfigurationTest extends BaseTestCase {
 
 	}
 
+	@Test
+	public void testName() {
+		Rocket rocket = TestRockets.makeFalcon9Heavy();
+		FlightConfiguration selected = rocket.getSelectedConfiguration();
+
+		// Test only motors or only manufacturers
+		selected.setName("[{motors}] - [{manufacturers}]");
+
+		selected.setAllStages();
+		assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0; 4\u00D7G77-0] - [[Rocket.motorCount.noStageMotors]; AeroTech; 4\u00D7AeroTech]", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("[[Rocket.motorCount.Nomotor]] - [[Rocket.motorCount.Nomotor]]", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("[; M1350-0; ] - [; AeroTech; ]", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("[; M1350-0; 4\u00D7G77-0] - [; AeroTech; 4\u00D7AeroTech]", selected.getName());
+
+
+		// Test combination of motors and manufacturers
+		selected.setName("[{motors  manufacturers}] -- [{manufacturers}] - [{motors}]");
+
+		selected.setAllStages();
+		assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0  AeroTech; 4\u00D7G77-0  AeroTech] -- [[Rocket.motorCount.noStageMotors]; AeroTech; 4\u00D7AeroTech] - [[Rocket.motorCount.noStageMotors]; M1350-0; 4\u00D7G77-0]", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("[[Rocket.motorCount.Nomotor]] -- [[Rocket.motorCount.Nomotor]] - [[Rocket.motorCount.Nomotor]]", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("[; M1350-0  AeroTech; ] -- [; AeroTech; ] - [; M1350-0; ]", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("[; M1350-0  AeroTech; 4\u00D7G77-0  AeroTech] -- [; AeroTech; 4\u00D7AeroTech] - [; M1350-0; 4\u00D7G77-0]", selected.getName());
+
+		// Test combination of manufacturers and motors
+		selected.setName("[{manufacturers | motors}]");
+
+		selected.setAllStages();
+		assertEquals("[[Rocket.motorCount.noStageMotors]; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("[[Rocket.motorCount.Nomotor]]", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("[; AeroTech | M1350-0; ]", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("[; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName());
+
+		// Test empty tags
+		selected.setName("{}");
+
+		selected.setAllStages();
+		assertEquals("{}", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("{}", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("{}", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("{}", selected.getName());
+
+		// Test invalid tags (1)
+		selected.setName("{motorsm}");
+
+		selected.setAllStages();
+		assertEquals("{motorsm}", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("{motorsm}", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("{motorsm}", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("{motorsm}", selected.getName());
+
+		// Test invalid tags (2)
+		selected.setName("{motors manufacturers '}");
+
+		selected.setAllStages();
+		assertEquals("{motors manufacturers '}", selected.getName());
+
+		selected.setOnlyStage(0);
+		assertEquals("{motors manufacturers '}", selected.getName());
+
+		selected.setOnlyStage(1);
+		assertEquals("{motors manufacturers '}", selected.getName());
+
+		selected.setAllStages();
+		selected._setStageActive(0, false);
+		assertEquals("{motors manufacturers '}", selected.getName());
+	}
+
 	@Test
 	public void testCopy() throws NoSuchFieldException, IllegalAccessException {
 		Rocket rocket = TestRockets.makeFalcon9Heavy();
 		FlightConfiguration original = rocket.getSelectedConfiguration();
+		original.setName("[{motors}] - [{manufacturers}]");
 		original.setOnlyStage(0);
 
 		// vvvv Test Target vvvv
diff --git a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java
new file mode 100644
index 000000000..5647485d2
--- /dev/null
+++ b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java
@@ -0,0 +1,509 @@
+package net.sf.openrocket.rocketcomponent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import net.sf.openrocket.rocketcomponent.position.AxialMethod;
+import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
+import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
+
+import net.sf.openrocket.util.TestRockets;
+import org.junit.Test;
+
+public class SymmetricComponentTest extends BaseTestCase {
+
+	@Test
+	public void testPreviousSymmetricComponent() {
+		Rocket rocket = TestRockets.makeFalcon9Heavy();
+		AxialStage payloadStage = rocket.getStage(0);
+		NoseCone payloadFairingNoseCone = (NoseCone) payloadStage.getChild(0);
+		BodyTube payloadBody = (BodyTube) payloadStage.getChild(1);
+		Transition payloadFairingTail = (Transition) payloadStage.getChild(2);
+		BodyTube upperStageBody = (BodyTube) payloadStage.getChild(3);
+		BodyTube interstageBody = (BodyTube) payloadStage.getChild(4);
+
+		assertNull(payloadFairingNoseCone.getPreviousSymmetricComponent());
+		assertEquals(payloadFairingNoseCone, payloadBody.getPreviousSymmetricComponent());
+		assertEquals(payloadBody, payloadFairingTail.getPreviousSymmetricComponent());
+		assertEquals(payloadFairingTail, upperStageBody.getPreviousSymmetricComponent());
+		assertEquals(upperStageBody, interstageBody.getPreviousSymmetricComponent());
+
+		AxialStage coreStage = rocket.getStage(1);
+		BodyTube coreBody = (BodyTube) coreStage.getChild(0);
+
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
+		NoseCone boosterCone = (NoseCone) boosterStage.getChild(0);
+		BodyTube boosterBody = (BodyTube) boosterStage.getChild(1);
+
+		assertNull(boosterCone.getPreviousSymmetricComponent());
+		assertEquals(boosterCone, boosterBody.getPreviousSymmetricComponent());
+	}
+
+	@Test
+	public void testPreviousSymmetricComponentInlineComponentAssembly() {
+		Rocket rocket = TestRockets.makeFalcon9Heavy();
+
+		// Stage 0
+		AxialStage payloadStage = rocket.getStage(0);
+		NoseCone payloadFairingNoseCone = (NoseCone) payloadStage.getChild(0);
+		BodyTube payloadBody = (BodyTube) payloadStage.getChild(1);
+		Transition payloadFairingTail = (Transition) payloadStage.getChild(2);
+		BodyTube upperStageBody = (BodyTube) payloadStage.getChild(3);
+		BodyTube interstageBody = (BodyTube) payloadStage.getChild(4);
+
+		// Stage 1
+		AxialStage coreStage = rocket.getStage(1);
+		BodyTube coreBody = (BodyTube) coreStage.getChild(0);
+
+		// Booster stage
+		ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
+		boosterStage.setInstanceCount(1);
+		boosterStage.setRadius(RadiusMethod.RELATIVE, 0);
+		NoseCone boosterCone = (NoseCone) boosterStage.getChild(0);
+		BodyTube boosterBody = (BodyTube) boosterStage.getChild(1);
+
+		// Add inline pod set
+		PodSet podSet = new PodSet();
+		podSet.setName("Inline Pod Set");
+		podSet.setInstanceCount(1);
+		podSet.setRadius(RadiusMethod.FREE, 0);
+		coreBody.addChild(podSet);
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 0);
+		NoseCone podSetCone = new NoseCone();
+		podSetCone.setLength(0.1);
+		podSetCone.setBaseRadius(0.05);
+		podSet.addChild(podSetCone);
+		BodyTube podSetBody = new BodyTube(0.2, 0.05, 0.001);
+		podSetBody.setName("Pod Set Body");
+		podSet.addChild(podSetBody);
+		TrapezoidFinSet finSet = new TrapezoidFinSet();
+		podSetBody.addChild(finSet);
+
+		// Add last stage
+		AxialStage lastStage = new AxialStage();
+		BodyTube lastStageBody = new BodyTube(0.2, 0.05, 0.001);
+		lastStageBody.setName("Last Stage Body");
+		lastStage.addChild(lastStageBody);
+		rocket.addChild(lastStage);
+
+		assertNull(payloadFairingNoseCone.getPreviousSymmetricComponent());
+		assertEquals(payloadFairingNoseCone, payloadBody.getPreviousSymmetricComponent());
+		assertEquals(payloadBody, payloadFairingTail.getPreviousSymmetricComponent());
+		assertEquals(payloadFairingTail, upperStageBody.getPreviousSymmetricComponent());
+		assertEquals(upperStageBody, interstageBody.getPreviousSymmetricComponent());
+
+		assertNull(boosterCone.getPreviousSymmetricComponent());
+		assertEquals(boosterCone, boosterBody.getPreviousSymmetricComponent());
+
+		// case 1: pod set is larger, and at the back of the core stage
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 2: pod set is smaller, and at the back of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 3: pod set is equal, and at the back of the core stage
+		podSetBody.setOuterRadius(0.0385);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 4: pod set is larger, and at the front of the core stage
+		podSetBody.setOuterRadius(0.05);
+		podSet.setAxialOffset(AxialMethod.TOP, 0);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 5: pod set is smaller, and at the front of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 6: pod set is equal, and at the front of the core stage
+		podSetBody.setOuterRadius(0.0385);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 7: pod set is same length as core stage, and larger, and at the front of the core stage
+		podSetBody.setOuterRadius(0.05);
+		podSetBody.setLength(0.7);
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 8: pod set is same length as core stage, and smaller, and at the front of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 9: pod set is in larger, and in the middle of the core stage
+		podSetBody.setLength(0.2);
+		podSetBody.setOuterRadius(0.05);
+		podSet.setAxialOffset(AxialMethod.MIDDLE, 0);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// case 10: pod set is in larger, and behind the back of the core stage
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 1);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+
+		// Add a booster inside the pod set
+		ParallelStage insideBooster = new ParallelStage();
+		insideBooster.setName("Inside Booster");
+		insideBooster.setInstanceCount(1);
+		insideBooster.setRadius(RadiusMethod.FREE, 0);
+		podSetBody.addChild(insideBooster);
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, 0);
+		BodyTube insideBoosterBody = new BodyTube(0.2, 0.06, 0.001);
+		insideBoosterBody.setName("Inside Booster Body");
+		insideBooster.addChild(insideBoosterBody);
+
+		// Case 1: inside booster is larger than pod set and flush to its end (both are at the back of the core stage)
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 0);
+		insideBoosterBody.setOuterRadius(0.06);
+		assertEquals(insideBoosterBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 2: inside booster is smaller than pod set and flush to its end
+		insideBoosterBody.setOuterRadius(0.04);
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 3: inside booster is equal the pod set and flush to its end
+		insideBoosterBody.setOuterRadius(0.05);
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 4: inside booster is larger than pod set and before the back (pod set at the back of the core stage)
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, -1);
+		insideBoosterBody.setOuterRadius(0.06);
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 5: inside booster is larger than pod set and after the back (pod set at the back of the core stage)
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, 1);
+		assertEquals(podSetBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetBody, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 6: inside booster is larger than pod set, pod set is before the back of the core stage, inside booster is an equal amount after the back of the pod set
+		podSet.setAxialOffset(AxialMethod.BOTTOM, -1.5);
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, 1.5);
+		assertEquals(insideBoosterBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetBody, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 7: inside booster is larger than pod set, pod set is after the back of the core stage, inside booster is an equal amount before the back of the pod set
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 1.5);
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, -1.5);
+		assertEquals(insideBoosterBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 8: inside booster is larger than pod set, pod set is before the back of the core stage, inside booster is flush with the back of the pod set
+		podSet.setAxialOffset(AxialMethod.BOTTOM, -1.5);
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, 0);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+
+		// Case 9: inside booster is larger than pod set, pod set is after the back of the core stage, inside booster is flush with the back of the pod set
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 1.5);
+		insideBooster.setAxialOffset(AxialMethod.BOTTOM, 0);
+		assertEquals(coreBody, lastStageBody.getPreviousSymmetricComponent());
+		assertEquals(coreBody, podSetCone.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getPreviousSymmetricComponent());
+		assertEquals(interstageBody, coreBody.getPreviousSymmetricComponent());
+		assertEquals(podSetCone, insideBoosterBody.getPreviousSymmetricComponent());
+	}
+
+	@Test
+	public void testNextSymmetricComponent() {
+		Rocket rocket = TestRockets.makeFalcon9Heavy();
+		AxialStage payloadStage = rocket.getStage(0);
+		NoseCone payloadFairingNoseCone = (NoseCone) payloadStage.getChild(0);
+		BodyTube payloadBody = (BodyTube) payloadStage.getChild(1);
+		Transition payloadFairingTail = (Transition) payloadStage.getChild(2);
+		BodyTube upperStageBody = (BodyTube) payloadStage.getChild(3);
+		BodyTube interstageBody = (BodyTube) payloadStage.getChild(4);
+
+		assertEquals(payloadBody, payloadFairingNoseCone.getNextSymmetricComponent());
+		assertEquals(payloadFairingTail, payloadBody.getNextSymmetricComponent());
+		assertEquals(upperStageBody, payloadFairingTail.getNextSymmetricComponent());
+		assertEquals(interstageBody, upperStageBody.getNextSymmetricComponent());
+
+		AxialStage coreStage = rocket.getStage(1);
+		BodyTube coreBody = (BodyTube) coreStage.getChild(0);
+
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertNull(coreBody.getNextSymmetricComponent());
+
+		ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
+		NoseCone boosterCone = (NoseCone) boosterStage.getChild(0);
+		BodyTube boosterBody = (BodyTube) boosterStage.getChild(1);
+
+		assertEquals(boosterBody, boosterCone.getNextSymmetricComponent());
+		assertNull(boosterBody.getNextSymmetricComponent());
+	}
+
+	@Test
+	public void testNextSymmetricComponentInlineComponentAssembly() {
+		Rocket rocket = TestRockets.makeFalcon9Heavy();
+
+		// Stage 0
+		AxialStage payloadStage = rocket.getStage(0);
+		NoseCone payloadFairingNoseCone = (NoseCone) payloadStage.getChild(0);
+		BodyTube payloadBody = (BodyTube) payloadStage.getChild(1);
+		Transition payloadFairingTail = (Transition) payloadStage.getChild(2);
+		BodyTube upperStageBody = (BodyTube) payloadStage.getChild(3);
+		BodyTube interstageBody = (BodyTube) payloadStage.getChild(4);
+
+		// Stage 1
+		AxialStage coreStage = rocket.getStage(1);
+		BodyTube coreBody = (BodyTube) coreStage.getChild(0);
+
+		// Booster stage
+		ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
+		boosterStage.setInstanceCount(1);
+		boosterStage.setRadius(RadiusMethod.RELATIVE, 0);
+		NoseCone boosterCone = (NoseCone) boosterStage.getChild(0);
+		BodyTube boosterBody = (BodyTube) boosterStage.getChild(1);
+
+		// Add inline pod set
+		PodSet podSet = new PodSet();
+		podSet.setName("Inline Pod Set");
+		podSet.setInstanceCount(1);
+		podSet.setRadius(RadiusMethod.FREE, 0);
+		coreBody.addChild(podSet);
+		podSet.setAxialOffset(AxialMethod.TOP, 0);
+		BodyTube podSetBody = new BodyTube(0.2, 0.05, 0.001);
+		podSetBody.setName("Pod Set Body");
+		podSet.addChild(podSetBody);
+		TrapezoidFinSet finSet = new TrapezoidFinSet();
+		podSetBody.addChild(finSet);
+		NoseCone podSetCone = new NoseCone();
+		podSetCone.setLength(0.1);
+		podSetCone.setBaseRadius(0.05);
+		podSetCone.setFlipped(true);
+		podSet.addChild(podSetCone);
+
+		// Add last stage
+		AxialStage lastStage = new AxialStage();
+		BodyTube lastStageBody = new BodyTube(0.2, 0.05, 0.001);
+		lastStageBody.setName("Last Stage Body");
+		lastStage.addChild(lastStageBody);
+		rocket.addChild(lastStage);
+
+		assertEquals(payloadBody, payloadFairingNoseCone.getNextSymmetricComponent());
+		assertEquals(payloadFairingTail, payloadBody.getNextSymmetricComponent());
+		assertEquals(upperStageBody, payloadFairingTail.getNextSymmetricComponent());
+		assertEquals(interstageBody, upperStageBody.getNextSymmetricComponent());
+
+		assertNull(lastStageBody.getNextSymmetricComponent());
+		assertEquals(boosterBody, boosterCone.getNextSymmetricComponent());
+
+		// case 1: pod set is larger, and at the front of the core stage
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 2: pod set is smaller, and at the front of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 3: pod set is equal, and at the front of the core stage
+		podSetBody.setOuterRadius(0.0385);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 4: pod set is larger, and at the back of the core stage
+		podSetBody.setOuterRadius(0.05);
+		podSet.setAxialOffset(AxialMethod.BOTTOM, 0);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 5: pod set is smaller, and at the back of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 6: pod set is equal, and at the back of the core stage
+		podSetBody.setOuterRadius(0.0385);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 7: pod set is same length as core stage, and larger, and at the back of the core stage
+		podSetBody.setOuterRadius(0.05);
+		podSetBody.setLength(0.7);
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 8: pod set is same length as core stage, and smaller, and at the back of the core stage
+		podSetBody.setOuterRadius(0.02);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 9: pod set is in larger, and in the middle of the core stage
+		podSetBody.setLength(0.2);
+		podSetBody.setOuterRadius(0.05);
+		podSet.setAxialOffset(AxialMethod.MIDDLE, 0);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// case 10: pod set is in larger, and behind the front of the core stage
+		podSet.setAxialOffset(AxialMethod.TOP, 1);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+
+		// Add a booster inside the pod set
+		ParallelStage insideBooster = new ParallelStage();
+		insideBooster.setName("Inside Booster");
+		insideBooster.setInstanceCount(1);
+		insideBooster.setRadius(RadiusMethod.FREE, 0);
+		podSetBody.addChild(insideBooster);
+		insideBooster.setAxialOffset(AxialMethod.TOP, 0);
+		BodyTube insideBoosterBody = new BodyTube(0.2, 0.06, 0.001);
+		insideBoosterBody.setName("Inside Booster Body");
+		insideBooster.addChild(insideBoosterBody);
+
+		// Case 1: inside booster is larger than pod set and flush to its front (both are at the front of the core stage)
+		podSet.setAxialOffset(AxialMethod.TOP, 0);
+		insideBoosterBody.setOuterRadius(0.06);
+		assertEquals(insideBoosterBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 2: inside booster is smaller than pod set and flush to its front
+		insideBoosterBody.setOuterRadius(0.04);
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 3: inside booster is equal the pod set and flush to its front
+		insideBoosterBody.setOuterRadius(0.05);
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 4: inside booster is larger than pod set and before the front (pod set at the front of the core stage)
+		insideBooster.setAxialOffset(AxialMethod.TOP, -1);
+		insideBoosterBody.setOuterRadius(0.06);
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 5: inside booster is larger than pod set and after the front (pod set at the front of the core stage)
+		insideBooster.setAxialOffset(AxialMethod.TOP, 1);
+		assertEquals(podSetBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 6: inside booster is larger than pod set, pod set is before the front of the core stage, inside booster is an equal amount after the front of the pod set
+		podSet.setAxialOffset(AxialMethod.TOP, -1.5);
+		insideBooster.setAxialOffset(AxialMethod.TOP, 1.5);
+		assertEquals(insideBoosterBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 7: inside booster is larger than pod set, pod set is after the front of the core stage, inside booster is an equal amount before the front of the pod set
+		podSet.setAxialOffset(AxialMethod.TOP, 1.5);
+		insideBooster.setAxialOffset(AxialMethod.TOP, -1.5);
+		assertEquals(insideBoosterBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 8: inside booster is larger than pod set, pod set is before the front of the core stage, inside booster is flush with the front of the pod set
+		podSet.setAxialOffset(AxialMethod.TOP, -1.5);
+		insideBooster.setAxialOffset(AxialMethod.TOP, 0);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+
+		// Case 9: inside booster is larger than pod set, pod set is after the front of the core stage, inside booster is flush with the front of the pod set
+		podSet.setAxialOffset(AxialMethod.TOP, 1.5);
+		insideBooster.setAxialOffset(AxialMethod.TOP, 0);
+		assertEquals(coreBody, interstageBody.getNextSymmetricComponent());
+		assertEquals(podSetCone, podSetBody.getNextSymmetricComponent());
+		assertNull(podSetCone.getNextSymmetricComponent());
+		assertEquals(lastStageBody, coreBody.getNextSymmetricComponent());
+		assertNull(insideBoosterBody.getNextSymmetricComponent());
+	}
+}
diff --git a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java
index e85ef71e3..0a2170188 100644
--- a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java
+++ b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java
@@ -8,443 +8,380 @@ import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
 import org.junit.Test;
 
 public class SymmetricComponentVolumeTest extends BaseTestCase {
-	
-	@Test
-	public void simpleConeFilled() {
-		NoseCone nc = new NoseCone();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setFilled(true);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setAftRadius(1.0);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.75, cg.x, epsilonPercent * 0.75);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleConeWithShoulderFilled() {
-		NoseCone nc = new NoseCone();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setFilled(true);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setAftRadius(1.0);
-		nc.setAftShoulderRadius(1.0);
-		nc.setAftShoulderLength(1.0);
-		nc.setAftShoulderThickness(1.0);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0;
-		volume += Math.PI;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume + "\t" + mass);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(1.312, cg.x, epsilonPercent * 1.071);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleConeHollow() {
-		NoseCone nc = new NoseCone();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.5);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0; // outer volume
-		
-		// manually projected Thickness of 0.5 on to radius to determine
-		// the innerConeDimen.  Since the outer cone is "square" (height = radius),
-		// we only need to compute this one dimension in order to compute the
-		// volume of the inner cone.
-		double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
-		double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
-		volume -= innerVolume;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.7454, cg.x, epsilonPercent * 0.7454);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleConeWithShoulderHollow() {
-		NoseCone nc = new NoseCone();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.5);
-		nc.setAftShoulderRadius(1.0);
-		nc.setAftShoulderLength(1.0);
-		nc.setAftShoulderThickness(0.5);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0; // outer volume
-		
-		// manually projected Thickness of 0.5 on to radius to determine
-		// the innerConeDimen.  Since the outer cone is "square" (height = radius),
-		// we only need to compute this one dimension in order to compute the
-		// volume of the inner cone.
-		double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
-		double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
-		volume -= innerVolume;
-		
-		volume += Math.PI - Math.PI * 0.5 * 0.5;
-		
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(1.2719, cg.x, epsilonPercent * 1.2719);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionFilled() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(4.0);
-		nc.setFilled(true);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(1.0);
-		nc.setAftRadius(2.0);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(2.4285, cg.x, epsilonPercent * 2.4285);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionWithShouldersFilled() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(4.0);
-		nc.setFilled(true);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(1.0);
-		nc.setAftRadius(2.0);
-		nc.setAftShoulderLength(1.0);
-		nc.setAftShoulderRadius(2.0);
-		nc.setAftShoulderThickness(2.0);
-		nc.setForeShoulderLength(1.0);
-		nc.setForeShoulderRadius(1.0);
-		nc.setForeShoulderThickness(1.0);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
-		// plus aft shoulder:
-		volume += Math.PI * 1.0 * 2.0 * 2.0;
-		// plus fore shoulder:
-		volume += Math.PI * 1.0 * 1.0 * 1.0;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(2.8023, cg.x, epsilonPercent * 2.8023);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionHollow1() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(0.5);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.5);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		// Volume of filled transition = 
-		double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
-		
-		// magic 2D cad drawing...
-		//
-		// Since the thickness >= fore radius, the
-		// hollowed out portion of the transition
-		// forms a cone.
-		// the dimensions of this cone were determined
-		// using a 2d cad tool.
-		
-		double innerConeRadius = 0.441;
-		double innerConeLength = 0.882;
-		double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
-		
-		double volume = filledVolume - innerVolume;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.5884, cg.x, epsilonPercent * 0.5884);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionWithShouldersHollow1() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(0.5);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.5);
-		nc.setAftShoulderLength(1.0);
-		nc.setAftShoulderRadius(1.0);
-		nc.setAftShoulderThickness(0.5);
-		nc.setForeShoulderLength(1.0);
-		nc.setForeShoulderRadius(0.5);
-		nc.setForeShoulderThickness(0.5); // note this means fore shoulder is filled.
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		// Volume of filled transition = 
-		double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
-		
-		// magic 2D cad drawing...
-		//
-		// Since the thickness >= fore radius, the
-		// hollowed out portion of the transition
-		// forms a cone.
-		// the dimensions of this cone were determined
-		// using a 2d cad tool.
-		
-		double innerConeRadius = 0.441;
-		double innerConeLength = 0.882;
-		double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
-		
-		double volume = filledVolume - innerVolume;
-		
-		// Now add aft shoulder
-		volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.5 * 0.5;
-		// Now add fore shoulder
-		volume += Math.PI * 1.0 * 0.5 * 0.5;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.8581, cg.x, epsilonPercent * 0.8581);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionHollow2() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(0.5);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.25);
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		// Volume of filled transition = 
-		double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
-		
-		// magic 2D cad drawing...
-		//
-		// Since the thickness < fore radius, the
-		// hollowed out portion of the transition
-		// forms a transition.
-		// the dimensions of this transition were determined
-		// using a 2d cad tool.
-		
-		double innerTransitionAftRadius = 0.7205;
-		double innerTransitionForeRadius = 0.2205;
-		double innerVolume = Math.PI / 3.0
-				* (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
-		
-		double volume = filledVolume - innerVolume;
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.56827, cg.x, epsilonPercent * 0.56827);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
-	@Test
-	public void simpleTransitionWithShouldersHollow2() {
-		Transition nc = new Transition();
-		
-		final double epsilonPercent = 0.001;
-		final double density = 2.0;
-		
-		nc.setLength(1.0);
-		nc.setType(Transition.Shape.CONICAL);
-		nc.setForeRadius(0.5);
-		nc.setAftRadius(1.0);
-		nc.setThickness(0.25);
-		nc.setAftShoulderLength(1.0);
-		nc.setAftShoulderRadius(1.0);
-		nc.setAftShoulderThickness(0.25);
-		nc.setForeShoulderLength(1.0);
-		nc.setForeShoulderRadius(0.5);
-		nc.setForeShoulderThickness(0.25);
-		
-		nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
-		
-		Coordinate cg = nc.getCG();
-		
-		//System.out.println(nc.getComponentVolume() + "\t" + nc.getMass());
-		//System.out.println(cg);
-		
-		// Volume of filled transition = 
-		double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
-		
-		// magic 2D cad drawing...
-		//
-		// Since the thickness < fore radius, the
-		// hollowed out portion of the transition
-		// forms a transition.
-		// the dimensions of this transition were determined
-		// using a 2d cad tool.
-		
-		double innerTransitionAftRadius = 0.7205;
-		double innerTransitionForeRadius = 0.2205;
-		double innerVolume = Math.PI / 3.0
-				* (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
-		
-		double volume = filledVolume - innerVolume;
-		
-		// now add aft shoulder
-		volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.75 * 0.75;
-		// now add fore shoulder
-		volume += Math.PI * 1.0 * 0.5 * 0.5 - Math.PI * 1.0 * 0.25 * 0.25;
-		
-		
-		double mass = density * volume;
-		
-		//System.out.println(volume);
-		
-		assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
-		assertEquals(mass, nc.getMass(), epsilonPercent * mass);
-		
-		assertEquals(0.7829, cg.x, epsilonPercent * 0.7829);
-		assertEquals(mass, cg.weight, epsilonPercent * mass);
-	}
-	
+
+    @Test
+    public void testVolumeSimpleConeFilled() {
+        NoseCone nc = new NoseCone();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setFilled(true);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setAftRadius(1.0);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+        double volume = Math.PI / 3.0;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.75, cg.x, epsilonPercent * 0.75);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleConeWithShoulderFilled() {
+        NoseCone nc = new NoseCone();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setFilled(true);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setAftRadius(1.0);
+        nc.setAftShoulderRadius(1.0);
+        nc.setAftShoulderLength(1.0);
+        nc.setAftShoulderThickness(1.0);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        double volume = Math.PI / 3.0;
+        volume += Math.PI;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(1.312, cg.x, epsilonPercent * 1.071);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleConeHollow() {
+        NoseCone nc = new NoseCone();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.5);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        double volume = Math.PI / 3.0; // outer volume
+
+        // manually projected Thickness of 0.5 on to radius to determine
+        // the innerConeDimen.  Since the outer cone is "square" (height = radius),
+        // we only need to compute this one dimension in order to compute the
+        // volume of the inner cone.
+        double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
+        double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
+        volume -= innerVolume;
+
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.7454, cg.x, epsilonPercent * 0.7454);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleConeWithShoulderHollow() {
+        NoseCone nc = new NoseCone();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.5);
+        nc.setAftShoulderRadius(1.0);
+        nc.setAftShoulderLength(1.0);
+        nc.setAftShoulderThickness(0.5);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        double volume = Math.PI / 3.0; // outer volume
+
+        // manually projected Thickness of 0.5 on to radius to determine
+        // the innerConeDimen.  Since the outer cone is "square" (height = radius),
+        // we only need to compute this one dimension in order to compute the
+        // volume of the inner cone.
+        double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
+        double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
+        volume -= innerVolume;
+        volume += Math.PI - Math.PI * 0.5 * 0.5;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(1.2719, cg.x, epsilonPercent * 1.2719);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionFilled() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(4.0);
+        nc.setFilled(true);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(1.0);
+        nc.setAftRadius(2.0);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+        double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(2.4285, cg.x, epsilonPercent * 2.4285);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionWithShouldersFilled() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(4.0);
+        nc.setFilled(true);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(1.0);
+        nc.setAftRadius(2.0);
+        nc.setAftShoulderLength(1.0);
+        nc.setAftShoulderRadius(2.0);
+        nc.setAftShoulderThickness(2.0);
+        nc.setForeShoulderLength(1.0);
+        nc.setForeShoulderRadius(1.0);
+        nc.setForeShoulderThickness(1.0);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
+        // plus aft shoulder:
+        volume += Math.PI * 1.0 * 2.0 * 2.0;
+        // plus fore shoulder:
+        volume += Math.PI * 1.0 * 1.0 * 1.0;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(2.8023, cg.x, epsilonPercent * 2.8023);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionHollow1() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(0.5);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.5);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        // Volume of filled transition =
+        double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
+
+        // magic 2D cad drawing...
+        //
+        // Since the thickness >= fore radius, the
+        // hollowed out portion of the transition
+        // forms a cone.
+        // the dimensions of this cone were determined
+        // using a 2d cad tool.
+
+        double innerConeRadius = 0.441;
+        double innerConeLength = 0.882;
+        double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
+        double volume = filledVolume - innerVolume;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.5884, cg.x, epsilonPercent * 0.5884);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionWithShouldersHollow1() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(0.5);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.5);
+        nc.setAftShoulderLength(1.0);
+        nc.setAftShoulderRadius(1.0);
+        nc.setAftShoulderThickness(0.5);
+        nc.setForeShoulderLength(1.0);
+        nc.setForeShoulderRadius(0.5);
+        nc.setForeShoulderThickness(0.5); // note this means fore shoulder is filled.
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        // Volume of filled transition =
+        double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
+
+        // magic 2D cad drawing...
+        //
+        // Since the thickness >= fore radius, the
+        // hollowed out portion of the transition
+        // forms a cone.
+        // the dimensions of this cone were determined
+        // using a 2d cad tool.
+
+        double innerConeRadius = 0.441;
+        double innerConeLength = 0.882;
+        double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
+
+        double volume = filledVolume - innerVolume;
+
+        // Now add aft shoulder
+        volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.5 * 0.5;
+        // Now add fore shoulder
+        volume += Math.PI * 1.0 * 0.5 * 0.5;
+
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.8581, cg.x, epsilonPercent * 0.8581);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionHollow2() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(0.5);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.25);
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        // Volume of filled transition =
+        double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
+
+        // magic 2D cad drawing...
+        //
+        // Since the thickness < fore radius, the
+        // hollowed out portion of the transition
+        // forms a transition.
+        // the dimensions of this transition were determined
+        // using a 2d cad tool.
+
+        double innerTransitionAftRadius = 0.7205;
+        double innerTransitionForeRadius = 0.2205;
+        double innerVolume = Math.PI / 3.0
+                * (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
+
+        double volume = filledVolume - innerVolume;
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.56827, cg.x, epsilonPercent * 0.56827);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
+
+    @Test
+    public void testVolumeSimpleTransitionWithShouldersHollow2() {
+        Transition nc = new Transition();
+
+        final double epsilonPercent = 0.001;
+        final double density = 2.0;
+
+        nc.setLength(1.0);
+        nc.setType(Transition.Shape.CONICAL);
+        nc.setForeRadius(0.5);
+        nc.setAftRadius(1.0);
+        nc.setThickness(0.25);
+        nc.setAftShoulderLength(1.0);
+        nc.setAftShoulderRadius(1.0);
+        nc.setAftShoulderThickness(0.25);
+        nc.setForeShoulderLength(1.0);
+        nc.setForeShoulderRadius(0.5);
+        nc.setForeShoulderThickness(0.25);
+
+        nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
+
+        Coordinate cg = nc.getCG();
+
+        // Volume of filled transition =
+        double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
+
+        // magic 2D cad drawing...
+        //
+        // Since the thickness < fore radius, the
+        // hollowed out portion of the transition
+        // forms a transition.
+        // the dimensions of this transition were determined
+        // using a 2d cad tool.
+
+        double innerTransitionAftRadius = 0.7205;
+        double innerTransitionForeRadius = 0.2205;
+        double innerVolume = Math.PI / 3.0
+                * (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
+
+        double volume = filledVolume - innerVolume;
+
+        // now add aft shoulder
+        volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.75 * 0.75;
+        // now add fore shoulder
+        volume += Math.PI * 1.0 * 0.5 * 0.5 - Math.PI * 1.0 * 0.25 * 0.25;
+
+
+        double mass = density * volume;
+
+        assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
+        assertEquals(mass, nc.getMass(), epsilonPercent * mass);
+
+        assertEquals(0.7829, cg.x, epsilonPercent * 0.7829);
+        assertEquals(mass, cg.weight, epsilonPercent * mass);
+    }
 }
diff --git a/swing/src/net/sf/openrocket/gui/components/StageSelector.java b/swing/src/net/sf/openrocket/gui/components/StageSelector.java
index 1609c16d9..3699e5dbd 100644
--- a/swing/src/net/sf/openrocket/gui/components/StageSelector.java
+++ b/swing/src/net/sf/openrocket/gui/components/StageSelector.java
@@ -14,6 +14,7 @@ import net.sf.openrocket.gui.widgets.SelectColorToggleButton;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.rocketcomponent.AxialStage;
 import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.ComponentAssembly;
 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
 import net.sf.openrocket.rocketcomponent.FlightConfiguration;
 import net.sf.openrocket.rocketcomponent.Rocket;
@@ -40,7 +41,7 @@ public class StageSelector extends JPanel implements StateChangeListener {
 	private void updateButtons( final FlightConfiguration configuration ) {
 		buttons.clear();
 		this.removeAll();
-		List<RocketComponent> assemblies = configuration.getRocket().getChildAssemblies();
+		List<ComponentAssembly> assemblies = configuration.getRocket().getAllChildAssemblies();
 
 		for (RocketComponent stage : assemblies) {
 			if (!(stage instanceof AxialStage)) continue;
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
index b768b81b6..ee295ee9f 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
@@ -1,5 +1,6 @@
 package net.sf.openrocket.gui.dialogs.flightconfiguration;
 
+import java.awt.Color;
 import java.awt.Dialog;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
@@ -12,6 +13,8 @@ import javax.swing.JPanel;
 import javax.swing.JTextField;
 
 import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.configdialog.CommonStrings;
 import net.sf.openrocket.gui.util.GUIUtil;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
@@ -28,7 +31,7 @@ public class RenameConfigDialog extends JDialog {
 		
 		JPanel panel = new JPanel(new MigLayout("fill"));
 		
-		panel.add(new JLabel(trans.get("RenameConfigDialog.lbl.name")), "span, wrap rel");
+		panel.add(new JLabel(trans.get("RenameConfigDialog.lbl.name") + " " + CommonStrings.dagger), "span, wrap rel");
 		
 		final JTextField textbox = new JTextField(rocket.getFlightConfiguration(fcid).getNameRaw());
 		panel.add(textbox, "span, w 200lp, growx, wrap para");
@@ -63,7 +66,15 @@ public class RenameConfigDialog extends JDialog {
 				RenameConfigDialog.this.setVisible(false);
 			}
 		});
-		panel.add(cancel);
+		panel.add(cancel, "wrap para");
+
+		// {motors} & {manufacturers} info
+		String text = "<html>" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors")
+				+ trans.get("RenameConfigDialog.lbl.infoManufacturers")
+				+ trans.get("RenameConfigDialog.lbl.infoCombination");
+		StyledLabel info = new StyledLabel(text, -2);
+		info.setFontColor(Color.DARK_GRAY);
+		panel.add(info, "spanx, growx, wrap");
 		
 		this.add(panel);
 		
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index b7fd17732..3c8f45839 100644
--- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
@@ -710,6 +710,8 @@ public class SimulationPanel extends JPanel {
 				tip += trans.get("simpanel.ttip.noData")+"<br>";
 				break;
 			case LOADED:
+				tip += trans.get("simpanel.ttip.loaded") + "<br>";
+				break;
 			case UPTODATE:
 				tip += trans.get("simpanel.ttip.uptodate") + "<br>";
 				break;
diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java
index 0ebd96ab0..bbbe1b052 100644
--- a/swing/src/net/sf/openrocket/gui/util/Icons.java
+++ b/swing/src/net/sf/openrocket/gui/util/Icons.java
@@ -33,7 +33,7 @@ public class Icons {
 		map.put(Simulation.Status.NOT_SIMULATED, loadImageIcon("pix/spheres/gray-16x16.png", "Not simulated"));
 		map.put(Simulation.Status.CANT_RUN, loadImageIcon("pix/spheres/yellow-16x16.png", "Can't run, no motors assigned."));
 		map.put(Simulation.Status.UPTODATE, loadImageIcon("pix/spheres/green-16x16.png", "Up to date"));
-		map.put(Simulation.Status.LOADED, loadImageIcon("pix/spheres/green-16x16.png", "Up to date"));
+		map.put(Simulation.Status.LOADED, loadImageIcon("pix/spheres/blue-16x16.png", "Loaded from File"));
 		map.put(Simulation.Status.OUTDATED, loadImageIcon("pix/spheres/red-16x16.png", "Out-of-date"));
 		map.put(Simulation.Status.EXTERNAL, loadImageIcon("pix/spheres/blue-16x16.png", "Imported data"));
 		SIMULATION_STATUS_ICON_MAP = Collections.unmodifiableMap(map);