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);