diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 0263884f5..3bbd7b2ff 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1864,6 +1864,10 @@ Warning.LargeAOA.str1 = Large angle of attack encountered. Warning.LargeAOA.str2 = Large angle of attack encountered ( Warning.DISCONTINUITY = Discontinuity in rocket body diameter Warning.OPEN_AIRFRAME_FORWARD = Forward end of airframe is open (radius is > 0) +Warning.AIRFRAME_GAP = Gap in rocket airframe +Warning.AIRFRAME_OVERLAP = Overlap in airframe components +Warning.PODSET_FORWARD = In-line podset forward of parent airframe component +Warning.PODSET_OVERLAP = In-line podset overlaps parent airframe component Warning.THICK_FIN = Thick fins may not simulate accurately. Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate. Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 550950226..bc75f02a6 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -19,6 +19,8 @@ import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.InstanceContext; import net.sf.openrocket.rocketcomponent.InstanceMap; +import net.sf.openrocket.rocketcomponent.ParallelStage; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent; @@ -279,6 +281,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { @Override public void checkGeometry(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){ Queue queue = new LinkedList<>(); + for (RocketComponent child : treeRoot.getChildren()) { // Ignore inactive stages if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { @@ -286,11 +289,19 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { } queue.add(child); } + + SymmetricComponent prevComp = null; + if ((treeRoot instanceof ComponentAssembly) && + (!(treeRoot instanceof Rocket)) && + (treeRoot.getChildCount() > 0)) { + prevComp = ((SymmetricComponent) (treeRoot.getChild(0))).getPreviousSymmetricComponent(); + } - SymmetricComponent prevComp = null; while(null != queue.peek()) { RocketComponent comp = queue.poll(); - if( comp instanceof SymmetricComponent ){ + if(( comp instanceof SymmetricComponent ) || + ((comp instanceof AxialStage) && + !(comp instanceof ParallelStage))) { for (RocketComponent child : comp.getChildren()) { // Ignore inactive stages if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { @@ -298,43 +309,85 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { } queue.add(child); } - - SymmetricComponent sym = (SymmetricComponent) comp; - prevComp = sym.getPreviousSymmetricComponent(); - if (prevComp == null) { - if (sym.getForeRadius() - sym.getThickness() > MathUtil.EPSILON) { - warnings.add(Warning.OPEN_AIRFRAME_FORWARD, sym.getName()); - } - } else { - // Check for radius discontinuity - // We're going to say it's discontinuous if it is presented to the user as having two different - // string representations. Hopefully there are enough digits in the string that it will - // present as different if the discontinuity is big enough to matter. - if (!UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*sym.getForeRadius()).equals(UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*prevComp.getAftRadius()))) { - // if ( !MathUtil.equals(sym.getForeRadius(), prevComp.getAftRadius())) { - warnings.add( Warning.DIAMETER_DISCONTINUITY, sym + ", " + prevComp); - } - } - // Check for phantom tube - if ((sym.getLength() < MathUtil.EPSILON) || - (sym.getAftRadius() < MathUtil.EPSILON && sym.getForeRadius() < MathUtil.EPSILON)) { - warnings.add(Warning.ZERO_VOLUME_BODY, sym.getName()); + if (comp instanceof SymmetricComponent) { + SymmetricComponent sym = (SymmetricComponent) comp; + if( null == prevComp){ + if (sym.getForeRadius() - sym.getThickness() > MathUtil.EPSILON) { + warnings.add(Warning.OPEN_AIRFRAME_FORWARD, sym.toString()); + } + } else { + // Check for radius discontinuity + // We're going to say it's discontinuous if it is presented to the user as having two different + // string representations. Hopefully there are enough digits in the string that it will + // present as different if the discontinuity is big enough to matter. + if (!UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*sym.getForeRadius()) + .equals(UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*prevComp.getAftRadius()))) { + warnings.add( Warning.DIAMETER_DISCONTINUITY, prevComp + ", " + sym); + } + + // Check for phantom tube + if ((sym.getLength() < MathUtil.EPSILON) || + (sym.getAftRadius() < MathUtil.EPSILON && sym.getForeRadius() < MathUtil.EPSILON)) { + warnings.add(Warning.ZERO_VOLUME_BODY, sym.getName()); + } + + // check for gap or overlap in airframe. We'll use a textual comparison to see if there is a + // gap or overlap, then use arithmetic comparison to see which it is. This won't be quite as reliable + // as the case for radius, since we never actually display the absolute X position + + double symXfore = sym.toAbsolute(Coordinate.NUL)[0].x; + double prevXfore = prevComp.toAbsolute(Coordinate.NUL)[0].x; + + double symXaft = sym.toAbsolute(new Coordinate(comp.getLength(), 0, 0, 0))[0].x; + double prevXaft = prevComp.toAbsolute(new Coordinate(prevComp.getLength(), 0, 0, 0))[0].x; + + if (!UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(symXfore) + .equals(UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(prevXaft))) { + if (symXfore > prevXaft) { + warnings.add(Warning.AIRFRAME_GAP, prevComp + ", " + sym); + } else { + // If we only have the component with a single forward compartment bring up + // a body component overlap message + if ((symXfore >= prevXfore) && + ((symXaft >= prevXaft) || (null == sym.getNextSymmetricComponent()))) { + warnings.add(Warning.AIRFRAME_OVERLAP, prevComp + ", " + sym); + } else { + // We have a PodSet that is either overlapping or completely forward of its parent component. + // We'll find the forward-most and aft-most components and figure out which + SymmetricComponent firstComp = prevComp; + SymmetricComponent scout = prevComp; + while (null != scout) { + firstComp = scout; + scout = scout.getPreviousSymmetricComponent(); + } + double firstCompXfore = firstComp.toAbsolute(Coordinate.NUL)[0].x; + + SymmetricComponent lastComp = sym; + scout = sym; + while (null != scout) { + lastComp = scout; + scout = scout.getNextSymmetricComponent(); + } + double lastCompXaft = lastComp.toAbsolute(new Coordinate(lastComp.getLength(), 0, 0, 0))[0].x; + + // completely forward vs. overlap + if (lastCompXaft <= firstCompXfore) { + warnings.add(Warning.PODSET_FORWARD, comp.getParent().toString()); + } else { + warnings.add(Warning.PODSET_OVERLAP, comp.getParent().toString()); + } + } + + } + } + } + prevComp = sym; } - - // double x = component.toAbsolute(Coordinate.NUL)[0].x; - // // Check for lengthwise discontinuity - // if (x > componentX + 0.0001) { - // if (!MathUtil.equals(radius, 0)) { - // warnings.add(Warning.DISCONTINUITY); - // radius = 0; - //} - //componentX = component.toAbsolute(new Coordinate(component.getLengthAerodynamic()))[0].x; - - }else if( comp instanceof ComponentAssembly ){ + } else if ((comp instanceof PodSet) || + (comp instanceof ParallelStage)) { checkGeometry(configuration, comp, warnings); } - } } diff --git a/core/src/net/sf/openrocket/aerodynamics/Warning.java b/core/src/net/sf/openrocket/aerodynamics/Warning.java index b3c31fecc..3c17c1e17 100644 --- a/core/src/net/sf/openrocket/aerodynamics/Warning.java +++ b/core/src/net/sf/openrocket/aerodynamics/Warning.java @@ -361,6 +361,18 @@ public abstract class Warning { /** A Warning that a ComponentAssembly has an open forward end */ public static final Warning OPEN_AIRFRAME_FORWARD = new Other(trans.get("Warning.OPEN_AIRFRAME_FORWARD")); + + /** A Warning that there is a gap in the airframe */ + public static final Warning AIRFRAME_GAP = new Other(trans.get("Warning.AIRFRAME_GAP")); + + /** A Warning that there are overlapping airframe components */ + public static final Warning AIRFRAME_OVERLAP = new Other(trans.get("Warning.AIRFRAME_OVERLAP")); + + /** A Warning that an inline podset is completely forward of its parent component */ + public static final Warning PODSET_FORWARD = new Other(trans.get("Warning.PODSET_FORWARD")); + + /** A Warning that an inline podset overlaps its parent component */ + public static final Warning PODSET_OVERLAP = new Other(trans.get("Warning.PODSET_OVERLAP")); /** A Warning that the fins are thick compared to the rocket body. */ ////Thick fins may not be modeled accurately. diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index f3152ef96..a4fcf4376 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -593,13 +593,14 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou * indicates a preferred radius, a negative value indicates that a * match was not found. */ + protected abstract double getRearAutoRadius(); /** * Return the previous symmetric component, or null if none exists. - * + * * @return the previous SymmetricComponent, or null. */ public final SymmetricComponent getPreviousSymmetricComponent() { @@ -611,8 +612,8 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou // (b) BodyTube -- for Parallel Stages & PodSets final RocketComponent grandParent = this.parent.getParent(); - int searchParentIndex = grandParent.getChildPosition(this.parent); // position of stage w/in parent - int searchSiblingIndex = this.parent.getChildPosition(this)-1; // guess at index of previous stage + int searchParentIndex = grandParent.getChildPosition(this.parent); // position of component w/in parent + int searchSiblingIndex = this.parent.getChildPosition(this)-1; // guess at index of previous component while( 0 <= searchParentIndex ) { final RocketComponent searchParent = grandParent.getChild(searchParentIndex); @@ -620,21 +621,27 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou if(searchParent instanceof ComponentAssembly){ while (0 <= searchSiblingIndex) { final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex); - if (searchSibling instanceof SymmetricComponent) { - SymmetricComponent candidate = (SymmetricComponent) searchSibling; - if (inline(candidate)) { - return candidate; - } - return null; + if ((searchSibling instanceof SymmetricComponent) && inline(searchSibling)) { + return (SymmetricComponent) searchSibling; } --searchSiblingIndex; } } + + // Look forward to the previous stage --searchParentIndex; + if( 0 <= searchParentIndex){ searchSiblingIndex = grandParent.getChild(searchParentIndex).getChildCount() - 1; } } + + // one last thing -- I could be the child of a PodSet, and in line with + // the SymmetricComponent that is my grandParent + if ((grandParent instanceof SymmetricComponent) && inline(grandParent)) { + return (SymmetricComponent) grandParent; + } + return null; } @@ -662,19 +669,30 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou if(searchParent instanceof ComponentAssembly){ while (searchSiblingIndex < searchParent.getChildCount()) { final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex); - if (searchSibling instanceof SymmetricComponent) { - SymmetricComponent candidate = (SymmetricComponent) searchSibling; - if (inline(candidate)) { - return candidate; - } - return null; + if ((searchSibling instanceof SymmetricComponent) && inline(searchSibling)) { + return (SymmetricComponent) searchSibling; } ++searchSiblingIndex; } } + + // Look aft to the next stage ++searchParentIndex; searchSiblingIndex = 0; } + + // One last thing -- I could have a child that is a PodSet that is in line + // with me + for (RocketComponent child : getChildren()) { + if (child instanceof PodSet) { + for (RocketComponent grandchild : child.getChildren()) { + if ((grandchild instanceof SymmetricComponent) && inline(grandchild)) { + return (SymmetricComponent) grandchild; + } + } + } + } + return null; } @@ -682,7 +700,7 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou * Determine whether a candidate symmetric component is in line with us * */ - private boolean inline(final SymmetricComponent candidate) { + private boolean inline(final RocketComponent candidate) { // if we share a parent, we are in line if (this.parent == candidate.parent) return true; diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index bb9ba1347..2471997b2 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -1743,10 +1743,69 @@ public class TestRockets { return rocket; } + // Alpha III modified to have an inline pod + public static final Rocket makeEstesAlphaIIIwithInlinePod() { + + Rocket rocket = TestRockets.makeEstesAlphaIII(); + + // Find rocket components to manipulate + final InstanceMap imap = rocket.getSelectedConfiguration().getActiveInstances(); + AxialStage stage = null; + BodyTube body = null; + + RocketComponent c = null; + for(Map.Entry> entry: imap.entrySet() ) { + c = entry.getKey(); + + // reference everything to the bottom + c.setAxialMethod(AxialMethod.BOTTOM); + + if (c instanceof AxialStage) { + stage = (AxialStage) c; + } + + if (c instanceof BodyTube) { + body = (BodyTube) c; + } + } + + // disconnect the body from the stage + stage.removeChild(body); + + // We need to reference the components hooked to the body to its aft end, not forward + + // Make a shorter copy of the body tube and connect it the Stage + // Notice -- total lengths of the short tubes must add up to match the original + BodyTube frontTube = new BodyTube(body.getLength()/2.0, body.getOuterRadius(), body.getThickness()); + frontTube.setName("Front Body Tube"); + stage.addChild(frontTube); + + // Add a PodSet to the front body tube. + PodSet pod = new PodSet(); + pod.setInstanceCount(1); + pod.setRadiusMethod(RadiusMethod.COAXIAL); + frontTube.addChild(pod); + pod.setAxialMethod(AxialMethod.TOP); + pod.setAxialOffset(frontTube.getLength()); + + // Add another even shorter tube to the pod + BodyTube middleTube = new BodyTube(body.getLength()/4.0, body.getOuterRadius(), body.getThickness()); + middleTube.setName("Middle Body Tube"); + pod.addChild(middleTube); + + // Shorten the original body tube, rename it, and put it on the pod + body.setName("Aft body tube"); + body.setLength(body.getLength()/4.0); + pod.addChild(body); + + return rocket; + + } + /** * dump a test rocket to a file, so we can open it in OR */ - static void dumpRocket(Rocket rocket, String filename) { + public static void dumpRocket(Rocket rocket, String filename) { OpenRocketDocument doc = OpenRocketDocumentFactory.createDocumentFromRocket(rocket); OpenRocketSaver saver = new OpenRocketSaver(); diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 2acd0f49c..9293773bc 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -19,6 +19,7 @@ import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.ParallelStage; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; @@ -442,4 +443,53 @@ public class BarrowmanCalculatorTest { assertEquals(" Estes Alpha III with multiple empty stages cp y value is incorrect:", cp_calcRef.y, cp_calcMulti.y , EPSILON); assertEquals(" Estes Alpha III with multiple empty stages cp z value is incorrect:", cp_calcRef.z, cp_calcMulti.z , EPSILON); } + + /** + * Tests in-line pod aerodynamics and warnings + * + */ + @Test + public void testInlinePods() { + WarningSet warnings = new WarningSet(); + + // reference rocket and results + final Rocket refRocket = TestRockets.makeEstesAlphaIII(); + final FlightConfiguration refConfig = refRocket.getSelectedConfiguration(); + final FlightConditions refConditions = new FlightConditions(refConfig); + + final BarrowmanCalculator refCalc = new BarrowmanCalculator(); + double refCP = refCalc.getCP(refConfig, refConditions, warnings).x; + final AerodynamicForces refForces = refCalc.getAerodynamicForces(refConfig, refConditions, warnings); + assertTrue("reference rocket should have no warnings", warnings.isEmpty()); + final double refCD = refForces.getCD(); + + // test rocket + final Rocket testRocket = TestRockets.makeEstesAlphaIIIwithInlinePod(); + final PodSet pod = (PodSet) testRocket.getChild(0).getChild(1).getChild(0); + 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); + assertTrue("test rocket should have no warnings", warnings.isEmpty()); + + assertEquals("ref and test rocket CP should match", refCP, testCP, EPSILON); + + final double testCD = testForces.getCD(); + assertEquals("ref and test rocket CD should match", refCD, testCD, EPSILON); + + // 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()); + + // move the pod forward. + warnings.clear(); + pod.setAxialOffset(pod.getAxialOffset() - 0.2); + testCP = testCalc.getCP(testConfig, testConditions, warnings).x; + assertFalse("should be warning from airframe overlap", warnings.isEmpty()); + } + }