From 074fee3663c68e0b702f7ca739d2dcfca3587c8e Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 21 Feb 2023 03:15:47 +0100 Subject: [PATCH] [#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;