Merge branch 'openrocket:unstable' into fix-2024

This commit is contained in:
Joe Pfeiffer 2023-02-25 17:56:01 -07:00 committed by GitHub
commit 1fa82c3fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1490 additions and 699 deletions

View File

@ -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

View File

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

View File

@ -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() {

View File

@ -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 {
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

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

View File

@ -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
*

View File

@ -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; }

View File

@ -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());
}
}

View File

@ -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

View File

@ -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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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