From f38c8a48c959ff77ad4507f9c5583079f5fac03b Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 14 Feb 2023 04:59:44 +0000 Subject: [PATCH 01/16] Use proper substitutor for flight config name --- .../rocketcomponent/FlightConfiguration.java | 36 +++---------------- 1 file changed, 5 insertions(+), 31 deletions(-) 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 Date: Tue, 14 Feb 2023 05:00:48 +0000 Subject: [PATCH 02/16] [#2055] Add manufacturers substitutor --- core/resources/l10n/messages.properties | 1 + .../MotorManufacturerSubstitutor.java | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index e7f8ae5e9..df5bc30a7 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -2263,6 +2263,7 @@ SeparationSelectionDialog.opt.override = Override for the {0} flight configurati MotorConfigurationPanel.description = Select the motors and motor ignition events of the selected flight configuration.
Motor mounts: Select which components function as motor mounts.
Motor configurations: 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/formatting/MotorManufacturerSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java new file mode 100644 index 000000000..0c75ebe29 --- /dev/null +++ b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java @@ -0,0 +1,144 @@ +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.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.HashMap; +import java.util.List; +import java.util.Map; + +@Plugin +public class MotorManufacturerSubstitutor implements RocketSubstitutor { + public static final String SUBSTITUTION = "{manufacturers}"; + + @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 = getMotorConfigurationManufacturer(rocket, configId); + return str.replace(SUBSTITUTION, description); + } + + @Override + public Map getDescriptions() { + Map desc = new HashMap<>(); + desc.put(SUBSTITUTION, trans.get("MotorManufacturerSubstitutor.description")); + return null; + } + + + + public String getMotorConfigurationManufacturer(Rocket rocket, FlightConfigurationId fcid) { + String manufacturers; + int motorCount = 0; + + // Generate the description + + // First iterate over each stage and store the manufacturer of each motor + List> list = new ArrayList<>(); + List currentList = Collections.emptyList(); + + 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() && motor instanceof ThrustCurveMotor) { + String manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName(); + + for (int i = 0; i < mount.getMotorCount(); i++) { + currentList.add(manufacturer); + motorCount++; + } + } + } + } + + if (motorCount == 0) { + return trans.get("Rocket.motorCount.Nomotor"); + } + + // Change multiple occurrences of a motor to n x motor + List stages = new ArrayList<>(); + + for (List 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); + } + + manufacturers = ""; + 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) + manufacturers = manufacturers + s; + else + manufacturers = manufacturers + "; " + s; + } + return manufacturers; + } +} + From 5fe03ed9f24a45693217b8c22d69bb5cd48585f5 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 14 Feb 2023 05:26:55 +0000 Subject: [PATCH 03/16] Add information on name tags --- core/resources/l10n/messages.properties | 3 +++ .../flightconfiguration/RenameConfigDialog.java | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index df5bc30a7..cf604e801 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -228,6 +228,9 @@ 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 '{motors}' will be replaced with the motor designation(s). +RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s). + ! Example design dialog exdesigndlg.but.open = Open 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..919bf1260 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,14 @@ public class RenameConfigDialog extends JDialog { RenameConfigDialog.this.setVisible(false); } }); - panel.add(cancel); + panel.add(cancel, "wrap para"); + + // {motors} & {manufacturers} info + String text = "" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors") + + "
" + trans.get("RenameConfigDialog.lbl.infoManufacturers"); + StyledLabel info = new StyledLabel(text, -1); + info.setFontColor(Color.DARK_GRAY); + panel.add(info, "spanx, growx, wrap"); this.add(panel); From 813f0d5fc6554dfd35d3436ad078c70b6c814830 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 14 Feb 2023 16:32:59 +0000 Subject: [PATCH 04/16] Only search active stages in substitutor --- .../MotorDescriptionSubstitutor.java | 24 ++++++------------- .../MotorManufacturerSubstitutor.java | 6 +++-- .../FlightConfigurationTest.java | 1 + 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java index f02f82540..f8d90741f 100644 --- a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java +++ b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java @@ -13,6 +13,7 @@ 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.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; @@ -56,23 +57,19 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { // First iterate over each stage and store the designations of each motor List> list = new ArrayList>(); List currentList = Collections.emptyList(); - - Iterator iterator = rocket.iterator(); - while (iterator.hasNext()) { - RocketComponent c = iterator.next(); - + + FlightConfiguration config = rocket.getFlightConfiguration(fcid); + for (RocketComponent c : rocket) { if (c instanceof AxialStage) { - - currentList = new ArrayList(); + 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() && motor != null) { + if (mount.isMotorMount() && config.isComponentActive(mount) && motor != null) { String designation = motor.getDesignation(inst.getEjectionDelay()); for (int i = 0; i < mount.getMotorCount(); i++) { @@ -80,7 +77,6 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { motorCount++; } } - } } @@ -99,11 +95,8 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { Collections.sort(stage); for (String current : stage) { if (current.equals(previous)) { - count++; - } else { - if (previous != null) { String s = ""; if (count > 1) { @@ -117,10 +110,8 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { else stageName = stageName + "," + s; } - previous = current; count = 1; - } } if (previous != null) { @@ -136,14 +127,13 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { else stageName = stageName + "," + s; } - stages.add(stageName); } name = ""; for (int i = 0; i < stages.size(); i++) { String s = stages.get(i); - if (s.equals("")) + if (s.equals("") && config.isStageActive(i)) s = trans.get("Rocket.motorCount.noStageMotors"); if (i == 0) name = name + s; diff --git a/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java index 0c75ebe29..bafd73361 100644 --- a/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java +++ b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java @@ -7,6 +7,7 @@ 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; @@ -56,6 +57,7 @@ public class MotorManufacturerSubstitutor implements RocketSubstitutor { List> list = new ArrayList<>(); List currentList = Collections.emptyList(); + FlightConfiguration config = rocket.getFlightConfiguration(fcid); for (RocketComponent c : rocket) { if (c instanceof AxialStage) { currentList = new ArrayList<>(); @@ -66,7 +68,7 @@ public class MotorManufacturerSubstitutor implements RocketSubstitutor { MotorConfiguration inst = mount.getMotorConfig(fcid); Motor motor = inst.getMotor(); - if (mount.isMotorMount() && motor instanceof ThrustCurveMotor) { + if (mount.isMotorMount() && config.isComponentActive(mount) && motor instanceof ThrustCurveMotor) { String manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName(); for (int i = 0; i < mount.getMotorCount(); i++) { @@ -131,7 +133,7 @@ public class MotorManufacturerSubstitutor implements RocketSubstitutor { manufacturers = ""; for (int i = 0; i < stages.size(); i++) { String s = stages.get(i); - if (s.equals("")) + if (s.equals("") && config.isStageActive(i)) s = trans.get("Rocket.motorCount.noStageMotors"); if (i == 0) manufacturers = manufacturers + s; diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index b671af3b3..28fc058dd 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -584,6 +584,7 @@ public class FlightConfigurationTest extends BaseTestCase { 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 From fb3a22d21006b3303cfa7a7ed773125fb8e1b307 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 14 Feb 2023 16:43:21 +0000 Subject: [PATCH 05/16] Add unit tests for config name --- .../FlightConfigurationTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index 28fc058dd..5f871c52e 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -580,6 +580,26 @@ public class FlightConfigurationTest extends BaseTestCase { } + @Test + public void testName() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + FlightConfiguration selected = rocket.getSelectedConfiguration(); + selected.setName("[{motors}] - [{manufacturers}]"); + + selected.setAllStages(); + assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0; 4×G77-0] - [[Rocket.motorCount.noStageMotors]; AeroTech; 4×AeroTech]", 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×G77-0] - [; AeroTech; 4×AeroTech]", selected.getName()); + } + @Test public void testCopy() throws NoSuchFieldException, IllegalAccessException { Rocket rocket = TestRockets.makeFalcon9Heavy(); From 5b921eebaf1e448951d1215fbff0719ea9bba466 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 14 Feb 2023 19:23:54 +0100 Subject: [PATCH 06/16] Fix non-ASCII characters --- .../openrocket/rocketcomponent/FlightConfigurationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index 5f871c52e..b516ea05a 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -587,7 +587,7 @@ public class FlightConfigurationTest extends BaseTestCase { selected.setName("[{motors}] - [{manufacturers}]"); selected.setAllStages(); - assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0; 4×G77-0] - [[Rocket.motorCount.noStageMotors]; AeroTech; 4×AeroTech]", selected.getName()); + 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()); @@ -597,7 +597,7 @@ public class FlightConfigurationTest extends BaseTestCase { selected.setAllStages(); selected._setStageActive(0, false); - assertEquals("[; M1350-0; 4×G77-0] - [; AeroTech; 4×AeroTech]", selected.getName()); + assertEquals("[; M1350-0; 4\u00D7G77-0] - [; AeroTech; 4\u00D7AeroTech]", selected.getName()); } @Test From 09918a3d220584916bc4ef7512fdae14fabd46b5 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 21 Feb 2023 03:13:27 +0100 Subject: [PATCH 07/16] Add clarifying comments --- .../openrocket/rocketcomponent/position/RadiusMethod.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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; } From 074fee3663c68e0b702f7ca739d2dcfca3587c8e Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 21 Feb 2023 03:15:47 +0100 Subject: [PATCH 08/16] [#2048] Support inline, flush assemblies in prev/next sym comp --- .../rocketcomponent/RocketComponent.java | 32 +- .../rocketcomponent/SymmetricComponent.java | 152 +++- .../aerodynamics/BarrowmanCalculatorTest.java | 12 +- .../SymmetricComponentTest.java | 511 +++++++++++ .../SymmetricComponentVolumeTest.java | 815 ++++++++---------- .../gui/components/StageSelector.java | 3 +- 6 files changed, 1069 insertions(+), 456 deletions(-) create mode 100644 core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java 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 getChildAssemblies() { + public final List getAllChildAssemblies() { checkState(); Iterator children = iterator(false); - List result = new ArrayList<>(); + List 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 getDirectChildAssemblies() { + checkState(); + + List 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/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 9293773bc..710f9780e 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); @@ -487,8 +486,15 @@ public class BarrowmanCalculatorTest { // move the pod forward. warnings.clear(); - pod.setAxialOffset(pod.getAxialOffset() - 0.2); - testCP = testCalc.getCP(testConfig, testConditions, warnings).x; + pod.setAxialOffset(pod.getAxialOffset() - 0.3); + testCP = testCalc.getCP(testConfig, testConditions, warnings).x; + assertFalse("should be warning from airframe overlap", warnings.isEmpty()); + + // move the pod back. + warnings.clear(); + pod.setAxialOffset(pod.getAxialOffset() + 0.1); + TestRockets.dumpRocket(testRocket, "/Users/SiboVanGool/Downloads/sfs/test.ork"); + testCP = testCalc.getCP(testConfig, testConditions, warnings).x; assertFalse("should be warning from airframe overlap", warnings.isEmpty()); } 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..62469418f --- /dev/null +++ b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java @@ -0,0 +1,511 @@ +package net.sf.openrocket.rocketcomponent; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.rocketcomponent.position.RadiusMethod; +import net.sf.openrocket.util.Coordinate; +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 assemblies = configuration.getRocket().getChildAssemblies(); + List assemblies = configuration.getRocket().getAllChildAssemblies(); for (RocketComponent stage : assemblies) { if (!(stage instanceof AxialStage)) continue; From 5be3e381e74f3550cd500ff431e8a208cc817268 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 22 Feb 2023 00:56:06 +0100 Subject: [PATCH 09/16] Update geometry check for new next/prev comp behavior --- .../aerodynamics/BarrowmanCalculator.java | 14 ++++++++++++++ .../aerodynamics/BarrowmanCalculatorTest.java | 7 +++---- 2 files changed, 17 insertions(+), 4 deletions(-) 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/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 710f9780e..f51b9ee99 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -482,20 +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.3); testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - assertFalse("should be warning from airframe overlap", warnings.isEmpty()); + assertEquals("should be warning from airframe overlap", 1, warnings.size()); // move the pod back. warnings.clear(); pod.setAxialOffset(pod.getAxialOffset() + 0.1); - TestRockets.dumpRocket(testRocket, "/Users/SiboVanGool/Downloads/sfs/test.ork"); testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - assertFalse("should be warning from airframe overlap", warnings.isEmpty()); + assertEquals("should be warning from podset airframe overlap", 1, warnings.size()); } } From a621bed20a6b4f18412e9c7fe47ca84a3e571e06 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 23 Feb 2023 16:15:30 +0100 Subject: [PATCH 10/16] [#2068] Use generalized motor config substitutor --- core/resources/l10n/messages.properties | 5 +- .../MotorConfigurationSubstitutor.java | 250 ++++++++++++++++++ .../MotorDescriptionSubstitutor.java | 148 ----------- .../MotorManufacturerSubstitutor.java | 146 ---------- .../FlightConfigurationTest.java | 83 ++++++ .../RenameConfigDialog.java | 5 +- 6 files changed, 339 insertions(+), 298 deletions(-) create mode 100644 core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java delete mode 100644 core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java delete mode 100644 core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index cf604e801..ffd308eb6 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -228,8 +228,9 @@ 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 '{motors}' will be replaced with the motor designation(s). -RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s). +RenameConfigDialog.lbl.infoMotors = The text '{motors}' will be replaced with the motor designation(s).
\te.g. '{motors} \u2192 'M1350-0'
+RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s).
\te.g. '{manufacturers}' \u2192 'AeroTech'
+RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{motors, manufacturers}' \u2192 'M1350-0, AeroTech'
! Example design dialog 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". + * + *

+ * 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 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 = new ArrayList<>(); + List 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 stages = new ArrayList<>(); + for (List 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 f8d90741f..000000000 --- a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java +++ /dev/null @@ -1,148 +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.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; - -@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 getDescriptions() { - Map desc = new HashMap(); - 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 = new ArrayList>(); - List currentList = Collections.emptyList(); - - 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 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 stages = new ArrayList(); - - for (List 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("") && config.isStageActive(i)) - 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/formatting/MotorManufacturerSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java deleted file mode 100644 index bafd73361..000000000 --- a/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java +++ /dev/null @@ -1,146 +0,0 @@ -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.HashMap; -import java.util.List; -import java.util.Map; - -@Plugin -public class MotorManufacturerSubstitutor implements RocketSubstitutor { - public static final String SUBSTITUTION = "{manufacturers}"; - - @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 = getMotorConfigurationManufacturer(rocket, configId); - return str.replace(SUBSTITUTION, description); - } - - @Override - public Map getDescriptions() { - Map desc = new HashMap<>(); - desc.put(SUBSTITUTION, trans.get("MotorManufacturerSubstitutor.description")); - return null; - } - - - - public String getMotorConfigurationManufacturer(Rocket rocket, FlightConfigurationId fcid) { - String manufacturers; - int motorCount = 0; - - // Generate the description - - // First iterate over each stage and store the manufacturer of each motor - List> list = new ArrayList<>(); - List currentList = Collections.emptyList(); - - 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 instanceof ThrustCurveMotor) { - String manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName(); - - for (int i = 0; i < mount.getMotorCount(); i++) { - currentList.add(manufacturer); - motorCount++; - } - } - } - } - - if (motorCount == 0) { - return trans.get("Rocket.motorCount.Nomotor"); - } - - // Change multiple occurrences of a motor to n x motor - List stages = new ArrayList<>(); - - for (List 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); - } - - manufacturers = ""; - 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"); - if (i == 0) - manufacturers = manufacturers + s; - else - manufacturers = manufacturers + "; " + s; - } - return manufacturers; - } -} - diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index b516ea05a..372d3f2de 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -584,6 +584,8 @@ public class FlightConfigurationTest extends BaseTestCase { public void testName() { Rocket rocket = TestRockets.makeFalcon9Heavy(); FlightConfiguration selected = rocket.getSelectedConfiguration(); + + // Test only motors or only manufacturers selected.setName("[{motors}] - [{manufacturers}]"); selected.setAllStages(); @@ -598,6 +600,87 @@ public class FlightConfigurationTest extends BaseTestCase { 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 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 919bf1260..ee295ee9f 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java @@ -70,8 +70,9 @@ public class RenameConfigDialog extends JDialog { // {motors} & {manufacturers} info String text = "" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors") - + "
" + trans.get("RenameConfigDialog.lbl.infoManufacturers"); - StyledLabel info = new StyledLabel(text, -1); + + 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"); From c1c6bac0db41d47e2013daf251e1b530d1796a48 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 23 Feb 2023 18:41:22 +0100 Subject: [PATCH 11/16] [#2068] Use generalized motor config substitutor --- core/resources/l10n/messages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index ffd308eb6..4198b6aae 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -230,7 +230,7 @@ RenameConfigDialog.lbl.name = Name for flight configuration: RenameConfigDialog.but.reset = Reset to default RenameConfigDialog.lbl.infoMotors = The text '{motors}' will be replaced with the motor designation(s).

\te.g. '{motors} \u2192 'M1350-0'
RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s).
\te.g. '{manufacturers}' \u2192 'AeroTech'
-RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{motors, manufacturers}' \u2192 'M1350-0, AeroTech'
+RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{motors manufacturers}' \u2192 'M1350-0 AeroTech'
! Example design dialog From 2acb1cae55ab0e4b1230ab4b26e7865f45a99614 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 23 Feb 2023 18:56:22 +0100 Subject: [PATCH 12/16] Update info text --- core/resources/l10n/messages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 4198b6aae..e98049aee 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -230,7 +230,7 @@ RenameConfigDialog.lbl.name = Name for flight configuration: RenameConfigDialog.but.reset = Reset to default RenameConfigDialog.lbl.infoMotors = The text '{motors}' will be replaced with the motor designation(s).
\te.g. '{motors} \u2192 'M1350-0'
RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s).
\te.g. '{manufacturers}' \u2192 'AeroTech'
-RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{motors manufacturers}' \u2192 'M1350-0 AeroTech'
+RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{manufacturers motors}' \u2192 'AeroTech M1350-0'
! Example design dialog From 297ab11fe2ad936bbb0d8876526ae134fd96e60f Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 23 Feb 2023 14:23:31 -0700 Subject: [PATCH 13/16] Don't bail on importing sim data when there is no tag; we can still have summary data --- .../file/openrocket/importt/FlightDataHandler.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 attributes, String content, WarningSet warnings) { - // If no 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 +} From f0621e5790a33a85a0083e10a5d556a2428af8fe Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 23 Feb 2023 15:15:54 -0700 Subject: [PATCH 14/16] Change simulation file loader so it always marks a simulation as LOADED if it has data, and NOT_SIMULATED if it doesn't. This is more reliable than the status attributed, as that gets saved as notsimulated when only summary data is saved. Also clean up logic in Simulation constructor a bit, --- .../sf/openrocket/document/Simulation.java | 40 +++++-------------- .../importt/SingleSimulationHandler.java | 20 +++++----- 2 files changed, 21 insertions(+), 39 deletions(-) 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/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 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 +} From 9a078f0e689c3400a79c1fead2db93abf0a1d879 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 23 Feb 2023 15:37:05 -0700 Subject: [PATCH 15/16] Mark simulations loaded from .ork file with blue sphere (same as data from external sources -- do we actually have a way to load data from external sources?) and appropriate tooltip --- core/resources/l10n/messages.properties | 1 + swing/src/net/sf/openrocket/gui/main/SimulationPanel.java | 2 ++ swing/src/net/sf/openrocket/gui/util/Icons.java | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 5a590191d..137fdf3ea 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -551,6 +551,7 @@ simpanel.col.Timetoapogee = Time to apogee simpanel.col.Flighttime = Flight time simpanel.col.Groundhitvelocity = Ground hit velocity simpanel.ttip.uptodate = Up to date +simpanel.ttip.loaded = Loaded from file simpanel.ttip.outdated = Out of date
Click Run simulations to simulate. simpanel.ttip.external = Imported data simpanel.ttip.notSimulated = Not simulated yet
Click Run simulations to simulate. 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")+"
"; break; case LOADED: + tip += trans.get("simpanel.ttip.loaded") + "
"; + break; case UPTODATE: tip += trans.get("simpanel.ttip.uptodate") + "
"; 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); From 5ba08cf8a468b657aa9f6ba049d7cf8512bcf503 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 23 Feb 2023 23:40:22 +0100 Subject: [PATCH 16/16] Remove unused imports --- .../sf/openrocket/rocketcomponent/SymmetricComponentTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java index 62469418f..5647485d2 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentTest.java @@ -2,10 +2,8 @@ package net.sf.openrocket.rocketcomponent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.rocketcomponent.position.RadiusMethod; -import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import net.sf.openrocket.util.TestRockets;