diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 526dacf32..b85074405 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -140,12 +140,11 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { WarningSet warnings) { // forces for this component, and all forces below it, in the rocket-tree - // => regardless `if(comp isinstance ComponentAssembly)`, or not. AerodynamicForces aggregateForces = new AerodynamicForces().zero(); aggregateForces.setComponent(comp); // forces for this component, _only_ - if(comp.isAerodynamic()) { + if(comp.isAerodynamic() || comp instanceof ComponentAssembly) { RocketComponentCalc calcObj = calcMap.get(comp); if (null == calcObj) { throw new NullPointerException("Could not find a CalculationObject for aerodynamic Component!: " + comp.getComponentName()); @@ -163,6 +162,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { continue; } + // forces particular to each component AerodynamicForces childForces = calculateForceAnalysis(configuration, conds, child, instances, eachForces, assemblyForces, warnings); @@ -173,7 +173,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { assemblyForces.put(comp, aggregateForces); - return eachForces.get(comp); + return assemblyForces.get(comp); } @Override @@ -944,7 +944,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { calcMap = new HashMap<>(); for (RocketComponent comp: configuration.getAllComponents()) { - if (!comp.isAerodynamic()) { + if (!comp.isAerodynamic() && !(comp instanceof ComponentAssembly)) { continue; } diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java new file mode 100644 index 000000000..2017bb168 --- /dev/null +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java @@ -0,0 +1,37 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Transformation; + +/* + * Aerodynamic properties of a component assembly. Since an "assembly" + * has no impact except as a summation of its subparts, just returns 0 + * + */ +public class ComponentAssemblyCalc extends RocketComponentCalc { + public ComponentAssemblyCalc(RocketComponent c) { + super(c); + } + + @Override + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, + AerodynamicForces forces, WarningSet warnings) { + // empty + } + + @Override + public double calculateFrictionCD(FlightConditions conditions, double componentCf, WarningSet warnings) { + return 0; + } + + @Override + public double calculatePressureCD(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + return 0; + } + + +} diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index 3143eb83d..9a75dbbf8 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -308,7 +308,7 @@ public class Simulation implements ChangeSource, Cloneable { */ public Status getStatus() { mutex.verify(); - if (status == Status.UPTODATE || status == Status.LOADED) { + if (isStatusUpToDate(status)) { if (rocket.getFunctionalModID() != simulatedRocketID || !options.equals(simulatedConditions)) { status = Status.OUTDATED; } @@ -330,6 +330,14 @@ public class Simulation implements ChangeSource, Cloneable { return status; } + + /** + * Returns true is the status indicates that the simulation data is up-to-date. + * @param status status of the simulation to check for if its data is up-to-date + */ + public static boolean isStatusUpToDate(Status status) { + return status == Status.UPTODATE || status == Status.LOADED || status == Status.EXTERNAL; + } diff --git a/core/src/net/sf/openrocket/file/rocksim/RockSimCommonConstants.java b/core/src/net/sf/openrocket/file/rocksim/RockSimCommonConstants.java index c76df9977..24ae530a5 100644 --- a/core/src/net/sf/openrocket/file/rocksim/RockSimCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rocksim/RockSimCommonConstants.java @@ -29,6 +29,8 @@ public class RockSimCommonConstants { public static final String DENSITY_TYPE = "DensityType"; public static final String RADIAL_LOC = "RadialLoc"; public static final String RADIAL_ANGLE = "RadialAngle"; + public static final String AUTO_CALC_RADIAL_DISTANCE = "AutoCalcRadialDistance"; + public static final String AUTO_CALC_RADIAL_ANGLE = "AutoCalcRadialAngle"; public static final String LOCATION_MODE = "LocationMode"; public static final String FINISH_CODE = "FinishCode"; public static final String SERIAL_NUMBER = "SerialNo"; diff --git a/core/src/net/sf/openrocket/file/rocksim/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rocksim/export/BodyTubeDTO.java index 479b35132..0eeb7c8f3 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/BodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/BodyTubeDTO.java @@ -11,6 +11,7 @@ import net.sf.openrocket.rocketcomponent.InnerTube; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.MassObject; import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Streamer; import net.sf.openrocket.rocketcomponent.Transition; @@ -50,6 +51,7 @@ public class BodyTubeDTO extends BasePartDTO implements AttachableParts { @XmlElementRefs({ @XmlElementRef(name = RockSimCommonConstants.BODY_TUBE, type = BodyTubeDTO.class), @XmlElementRef(name = RockSimCommonConstants.BODY_TUBE, type = InnerBodyTubeDTO.class), + @XmlElementRef(name = RockSimCommonConstants.TRANSITION, type = TransitionDTO.class), @XmlElementRef(name = RockSimCommonConstants.RING, type = CenteringRingDTO.class), @XmlElementRef(name = RockSimCommonConstants.LAUNCH_LUG, type = LaunchLugDTO.class), @XmlElementRef(name = RockSimCommonConstants.FIN_SET, type = FinSetDTO.class), @@ -57,7 +59,8 @@ public class BodyTubeDTO extends BasePartDTO implements AttachableParts { @XmlElementRef(name = RockSimCommonConstants.TUBE_FIN_SET, type = TubeFinSetDTO.class), @XmlElementRef(name = RockSimCommonConstants.STREAMER, type = StreamerDTO.class), @XmlElementRef(name = RockSimCommonConstants.PARACHUTE, type = ParachuteDTO.class), - @XmlElementRef(name = RockSimCommonConstants.MASS_OBJECT, type = MassObjectDTO.class)}) + @XmlElementRef(name = RockSimCommonConstants.MASS_OBJECT, type = MassObjectDTO.class), + @XmlElementRef(name = RockSimCommonConstants.EXTERNAL_POD, type = PodSetDTO.class)}) List attachedParts = new ArrayList(); /** @@ -125,6 +128,8 @@ public class BodyTubeDTO extends BasePartDTO implements AttachableParts { addAttachedPart(new FinSetDTO((FinSet) rocketComponents)); } else if (rocketComponents instanceof TubeFinSet) { addAttachedPart(new TubeFinSetDTO((TubeFinSet) rocketComponents)); + } else if (rocketComponents instanceof PodSet) { + addAttachedPart(new PodSetDTO((PodSet) rocketComponents)); } } } diff --git a/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java b/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java new file mode 100644 index 000000000..7baa9b56f --- /dev/null +++ b/core/src/net/sf/openrocket/file/rocksim/export/PodSetDTO.java @@ -0,0 +1,110 @@ +package net.sf.openrocket.file.rocksim.export; + +import net.sf.openrocket.file.rocksim.RockSimCommonConstants; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.PodSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.rocketcomponent.position.RadiusMethod; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.List; + +/** + * Models the XML element for a RockSim pod. + */ +@XmlRootElement(name = RockSimCommonConstants.EXTERNAL_POD) +@XmlAccessorType(XmlAccessType.FIELD) +public class PodSetDTO extends BasePartDTO implements AttachableParts { + @XmlElement(name = RockSimCommonConstants.AUTO_CALC_RADIAL_DISTANCE) + private int autoCalcRadialDistance = 0; + @XmlElement(name = RockSimCommonConstants.AUTO_CALC_RADIAL_ANGLE) + private int autoCalcRadialAngle = 0; + @XmlElementWrapper(name = RockSimCommonConstants.ATTACHED_PARTS) + @XmlElementRefs({ + @XmlElementRef(name = RockSimCommonConstants.BODY_TUBE, type = BodyTubeDTO.class), + @XmlElementRef(name = RockSimCommonConstants.NOSE_CONE, type = NoseConeDTO.class), + @XmlElementRef(name = RockSimCommonConstants.TRANSITION, type = TransitionDTO.class), + @XmlElementRef(name = RockSimCommonConstants.EXTERNAL_POD, type = PodSetDTO.class)}) + List attachedParts = new ArrayList(); + + /** + * Default constructor. + */ + public PodSetDTO() { + } + + /** + * Copy constructor. Fully populates this instance with values taken from the OR PodSet. + * + * @param theORPodSet + */ + public PodSetDTO(PodSet theORPodSet) { + super(theORPodSet); + // OR should always override the radial angle and distance + setAutoCalcRadialDistance(false); + setAutoCalcRadialAngle(false); + setRadialAngle(theORPodSet.getAngleOffset()); + setRadialLoc(theORPodSet.getRadiusMethod().getRadius( + theORPodSet.getParent(), theORPodSet, + theORPodSet.getRadiusOffset()) * RockSimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); + setXb(theORPodSet.getAxialOffset(AxialMethod.TOP) * RockSimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); + + for (RocketComponent child : theORPodSet.getChildren()) { + if (child instanceof PodSet) { + addAttachedPart(new PodSetDTO((PodSet) child)); + } else if (child instanceof BodyTube) { + addAttachedPart(new BodyTubeDTO((BodyTube) child)); + } else if (child instanceof NoseCone) { + addAttachedPart(new NoseConeDTO((NoseCone) child)); + } else if (child instanceof Transition) { + addAttachedPart(new TransitionDTO((Transition) child)); + } + } + } + + public int getAutoCalcRadialDistance() { + return autoCalcRadialDistance; + } + + public void setAutoCalcRadialDistance(boolean motorMount) { + if (motorMount) { + this.autoCalcRadialDistance = 1; + } else { + this.autoCalcRadialDistance = 0; + } + } + + public int getAutoCalcRadialAngle() { + return autoCalcRadialAngle; + } + + public void setAutoCalcRadialAngle(boolean motorMount) { + if (motorMount) { + this.autoCalcRadialAngle = 1; + } else { + this.autoCalcRadialAngle = 0; + } + } + + @Override + public void addAttachedPart(BasePartDTO part) { + if (!attachedParts.contains(part)) { + attachedParts.add(part); + } + } + + @Override + public void removeAttachedPart(BasePartDTO part) { + attachedParts.remove(part); + } +} diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java index 03563fd3d..d90b8b7c5 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java @@ -8,6 +8,7 @@ import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import java.util.HashMap; @@ -68,23 +69,32 @@ class AttachedPartsHandler extends AbstractElementHandler { return new RingHandler(context, component, warnings); } if (RockSimCommonConstants.BODY_TUBE.equals(element)) { - return new InnerBodyTubeHandler(context, component, warnings); + // Pods can have BodyTubes as attached parts, but not inner tubes. All other components can't have BodyTubes as + // attached parts. + if (component instanceof PodSet) { + return new BodyTubeHandler(context, component, warnings); + } else { + return new InnerBodyTubeHandler(context, component, warnings); + } } if (RockSimCommonConstants.TRANSITION.equals(element)) { return new TransitionHandler(context, component, warnings); } + if (RockSimCommonConstants.NOSE_CONE.equals(element)) { + return new NoseConeHandler(context, component, warnings); + } if (RockSimCommonConstants.SUBASSEMBLY.equals(element)) { return new SubAssemblyHandler(context, component); } if (RockSimCommonConstants.TUBE_FIN_SET.equals(element)) { return new TubeFinSetHandler(context, component, warnings); } + if (RockSimCommonConstants.EXTERNAL_POD.equals(element)) { + return new PodHandler(context, component, warnings); + } if (RockSimCommonConstants.RING_TAIL.equals(element)) { warnings.add("Ring tails are not currently supported. Ignoring."); } - if (RockSimCommonConstants.EXTERNAL_POD.equals(element)) { - warnings.add("Pods are not currently supported. Ignoring."); - } return null; } } diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java index 84ed5a8ce..093858955 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java @@ -25,6 +25,7 @@ class BodyTubeHandler extends BaseHandler { * The OpenRocket BodyTube. */ private final BodyTube bodyTube; + private int isInsideTube = 0; /** * Constructor. @@ -80,6 +81,9 @@ class BodyTubeHandler extends BaseHandler { if (RockSimCommonConstants.MATERIAL.equals(element)) { setMaterialName(content); } + if (RockSimCommonConstants.IS_INSIDE_TUBE.equals(element)) { + isInsideTube = Integer.parseInt(content); + } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); } @@ -104,4 +108,11 @@ class BodyTubeHandler extends BaseHandler { public Material.Type getMaterialType() { return Material.Type.BULK; } + + /** + * Returns 0 if this is a body tube, 1 if it is an inside tube. + */ + public int isInsideTube() { + return isInsideTube; + } } diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java new file mode 100644 index 000000000..ffec3d0b5 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java @@ -0,0 +1,66 @@ +package net.sf.openrocket.file.rocksim.importt; + +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rocksim.RockSimCommonConstants; +import net.sf.openrocket.file.simplesax.ElementHandler; +import net.sf.openrocket.file.simplesax.PlainTextHandler; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.PodSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.position.RadiusMethod; +import org.xml.sax.SAXException; + +import java.util.HashMap; + +public class PodHandler extends PositionDependentHandler { + /** + * The OpenRocket BodyTube. + */ + private final PodSet podSet; + + public PodHandler(DocumentLoadingContext context, RocketComponent c, WarningSet warnings) { + super(context); + if (c == null) { + throw new IllegalArgumentException("The parent component of a pod set may not be null."); + } + podSet = new PodSet(); + podSet.setInstanceCount(1); // RockSim only supports one pod instance + podSet.setRadiusMethod(RadiusMethod.FREE); // RockSim radial offset is relative to the center of the parent + if (isCompatible(c, PodSet.class, warnings)) { + c.addChild(podSet); + } + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) throws SAXException { + if (RockSimCommonConstants.BODY_TUBE.equals(element)) { // RockSim pods allow body tubes, not inner tubes + return new BodyTubeHandler(context, podSet, warnings); + } + if (RockSimCommonConstants.ATTACHED_PARTS.equals(element)) { + return new AttachedPartsHandler(context, podSet); + } + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { + super.closeElement(element, attributes, content, warnings); + if (RockSimCommonConstants.RADIAL_ANGLE.equals(element)) { + podSet.setAngleOffset(Double.parseDouble(content)); + } + if (RockSimCommonConstants.RADIAL_LOC.equals(element)) { + podSet.setRadiusOffset(Double.parseDouble(content) / RockSimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); + } + } + + @Override + protected PodSet getComponent() { + return podSet; + } + + @Override + protected Material.Type getMaterialType() { + return Material.Type.BULK; + } +} diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java index c5c5ae7ba..2bdd3a674 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java @@ -9,6 +9,8 @@ import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimLocationMode; +import net.sf.openrocket.rocketcomponent.ComponentAssembly; +import net.sf.openrocket.rocketcomponent.ParallelStage; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.position.AxialMethod; @@ -70,6 +72,10 @@ public abstract class PositionDependentHandler extend * Set the axialMethod of a component. */ protected void setLocation() { + if ((getComponent() instanceof ComponentAssembly || getComponent() instanceof ParallelStage) && + getComponent().getParent() == null) { + return; + } getComponent().setAxialMethod(axialMethod); if (axialMethod.equals(AxialMethod.BOTTOM)) { getComponent().setAxialOffset(-1d * positionValue); diff --git a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java index 18744d671..0c163a50a 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java @@ -121,19 +121,23 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { @Override public double getAxialOffset() { + return getAxialOffset(this.axialMethod); + } + @Override + public double getAxialOffset(AxialMethod method) { double returnValue; - + if (this.isAfter()){ // remember the implicit (this instanceof Stage) throw new BugException("found a pod positioned via: AFTER, but is not on the centerline?!: " + this.getName() + " is " + this.getAxialMethod().name() ); } else { - returnValue = super.getAxialOffset(this.axialMethod); + returnValue = super.getAxialOffset(method); } - + if (MathUtil.EPSILON > Math.abs(returnValue)) { returnValue = 0.0; } - + return returnValue; } diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 177181168..339c0704b 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -221,25 +221,16 @@ public class BasicEventSimulationEngine implements SimulationEngine { // and thrust < THRUST_TUMBLE_CONDITION threshold if (!currentStatus.isTumbling()) { - final double t = currentStatus.getFlightData().getLast(FlightDataType.TYPE_THRUST_FORCE); final double cp = currentStatus.getFlightData().getLast(FlightDataType.TYPE_CP_LOCATION); final double cg = currentStatus.getFlightData().getLast(FlightDataType.TYPE_CG_LOCATION); final double aoa = currentStatus.getFlightData().getLast(FlightDataType.TYPE_AOA); final boolean wantToTumble = (cg > cp && aoa > AOA_TUMBLE_CONDITION); - - if (wantToTumble) { - final boolean tooMuchThrust = t > THRUST_TUMBLE_CONDITION; - final boolean isSustainer = currentStatus.getConfiguration().isStageActive(0); - final boolean isApogee = currentStatus.isApogeeReached(); - if (tooMuchThrust) { - currentStatus.getWarnings().add(Warning.TUMBLE_UNDER_THRUST); - } else if (isApogee || !isSustainer) { - addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, currentStatus.getSimulationTime())); - currentStatus.setTumbling(true); - } - } - + final boolean isSustainer = currentStatus.getConfiguration().isStageActive(0); + final boolean isApogee = currentStatus.isApogeeReached(); + if (wantToTumble && (isApogee || !isSustainer)) { + addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, currentStatus.getSimulationTime())); + } } // If I'm on the ground and have no events in the queue, I'm done @@ -535,6 +526,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { currentStatus.getFlightData().addEvent(event); } + log.debug("deployed recovery devices: " + currentStatus.getDeployedRecoveryDevices().size() ); break; case GROUND_HIT: @@ -556,10 +548,19 @@ public class BasicEventSimulationEngine implements SimulationEngine { break; case TUMBLE: - if (!currentStatus.isLanded()) { - currentStepper = tumbleStepper; - currentStatus = currentStepper.initialize(currentStatus); - } + // Inhibit if we've deployed a parachute or we're on the ground + if ((currentStatus.getDeployedRecoveryDevices().size() > 0) || currentStatus.isLanded()) + break; + + currentStepper = tumbleStepper; + currentStatus = currentStepper.initialize(currentStatus); + + final boolean tooMuchThrust = currentStatus.getFlightData().getLast(FlightDataType.TYPE_THRUST_FORCE) > THRUST_TUMBLE_CONDITION; + if (tooMuchThrust) { + currentStatus.getWarnings().add(Warning.TUMBLE_UNDER_THRUST); + } + + currentStatus.setTumbling(true); currentStatus.getFlightData().addEvent(event); break; } diff --git a/core/src/net/sf/openrocket/simulation/FlightEvent.java b/core/src/net/sf/openrocket/simulation/FlightEvent.java index b78ad1f3b..e76d45e0b 100644 --- a/core/src/net/sf/openrocket/simulation/FlightEvent.java +++ b/core/src/net/sf/openrocket/simulation/FlightEvent.java @@ -146,15 +146,33 @@ public class FlightEvent implements Comparable { /** * Compares this event to another event depending on the event time. Secondary + * sorting is performed on stages; lower (numerically higher) stage first. Tertiary * sorting is performed based on the event type ordinal. */ @Override public int compareTo(FlightEvent o) { + + // first, sort on time if (this.time < o.time) return -1; if (this.time > o.time) return 1; + // second, sort on stage presence. Events with no source go first + if ((this.getSource() == null) && (o.getSource() != null)) + return -1; + if ((this.getSource() != null) && (o.getSource() == null)) + return 1; + + // third, sort on stage order. Bigger stage number goes first + if ((this.getSource() != null) && (o.getSource() != null)) { + if (this.getSource().getStageNumber() > o.getSource().getStageNumber()) + return -1; + if (this.getSource().getStageNumber() < o.getSource().getStageNumber()) + return 1; + } + + // finally, sort on event type return this.type.ordinal() - o.type.ordinal(); } diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 44dc7b827..bb9ba1347 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -217,7 +217,7 @@ public class TestRockets { .setDiameter(0.029) .setLength(0.124) .setTimePoints(new double[] { 0, 1, 2 }) - .setThrustPoints(new double[] { 0, 1, 0 }) + .setThrustPoints(new double[] { 0, 20, 0 }) .setCGPoints(new Coordinate[] { new Coordinate(.062, 0, 0, 0.123),new Coordinate(.062, 0, 0, .0935),new Coordinate(.062, 0, 0, 0.064)}) .setDigest("digest G77 test") diff --git a/core/test/net/sf/openrocket/file/rocksim/export/RockSimDocumentDTOTest.java b/core/test/net/sf/openrocket/file/rocksim/export/RockSimDocumentDTOTest.java index afaeef030..242a62c7f 100644 --- a/core/test/net/sf/openrocket/file/rocksim/export/RockSimDocumentDTOTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/export/RockSimDocumentDTOTest.java @@ -31,11 +31,14 @@ import net.sf.openrocket.rocketcomponent.InnerTube; import net.sf.openrocket.rocketcomponent.MassComponent; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.ShockCord; import net.sf.openrocket.rocketcomponent.Streamer; import net.sf.openrocket.rocketcomponent.TubeCoupler; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.rocketcomponent.position.RadiusMethod; import org.junit.Assert; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -94,6 +97,72 @@ public class RockSimDocumentDTOTest extends RockSimTestBase { output.delete(); } + /** + * Tests exporting a rocket with pods, and whether importing that same file results in the same pod configuration. + */ + @Test + public void testPodsExport() throws Exception { + OpenRocketDocument originalDocument = makePodsRocket(); + Rocket originalRocket = originalDocument.getRocket(); + + // Convert to RockSim XML + String result = new RockSimSaver().marshalToRockSim(originalDocument); + + // Write to .rkt file + Path output = Files.createTempFile("podsRocket", ".rkt"); + Files.write(output, result.getBytes(StandardCharsets.UTF_8)); + + // Read the file + RockSimLoader loader = new RockSimLoader(); + InputStream stream = new FileInputStream(output.toFile()); + Assert.assertNotNull("Could not open podsRocket.rkt", stream); + OpenRocketDocument importedDocument = OpenRocketDocumentFactory.createEmptyRocket(); + DocumentLoadingContext context = new DocumentLoadingContext(); + context.setOpenRocketDocument(importedDocument); + context.setMotorFinder(new DatabaseMotorFinder()); + loader.loadFromStream(context, new BufferedInputStream(stream)); + Rocket importedRocket = importedDocument.getRocket(); + + // Test children counts + List originalChildren = originalRocket.getAllChildren(); + List importedChildren = importedRocket.getAllChildren(); + assertEquals(" Number of total children doesn't match", + originalChildren.size(), importedChildren.size()); + assertEquals(" Number of rocket children doesn't match", 1, importedRocket.getChildCount()); + AxialStage stage = (AxialStage) importedRocket.getChild(0); + assertEquals(" Number of stage children doesn't match", 2, stage.getChildCount()); + BodyTube tube = (BodyTube) stage.getChild(1); + assertEquals(" Number of body tube children doesn't match", 3, tube.getChildCount()); + PodSet pod1 = (PodSet) tube.getChild(0); + assertEquals(" Number of pod 1 children doesn't match", 1, pod1.getChildCount()); + PodSet pod2 = (PodSet) tube.getChild(1); + assertEquals(" Number of pod 2 children doesn't match", 2, pod2.getChildCount()); + PodSet pod3 = (PodSet) tube.getChild(2); + assertEquals(" Number of pod 3 children doesn't match", 0, pod3.getChildCount()); + + // Test component names + for (int i = 1; i < originalChildren.size(); i++) { + assertEquals(" Child " + i + " does not match", + originalChildren.get(i).getName(), importedChildren.get(i).getName()); + } + + // Test pod parameters + assertEquals(-0.14, pod1.getAxialOffset(), 0.0001); + assertEquals(0.065, pod1.getRadiusOffset(), 0.0001); + assertEquals(Math.PI, pod1.getAngleOffset(), 0.0001); + assertEquals(1, pod1.getInstanceCount()); + assertEquals(0.02, pod2.getAxialOffset(), 0.0001); + assertEquals(0.025, pod2.getRadiusOffset(), 0.0001); + assertEquals(- Math.PI / 2, pod2.getAngleOffset(), 0.0001); + assertEquals(1, pod2.getInstanceCount()); + assertEquals(0.23, pod3.getAxialOffset(), 0.0001); + assertEquals(0.06, pod3.getRadiusOffset(), 0.0001); + assertEquals(Math.PI / 3, pod3.getAngleOffset(), 0.0001); + assertEquals(1, pod3.getInstanceCount()); + + stream.close(); + Files.delete(output); + } /** * Tests exporting a design where a tube coupler has children, which is not supported by RockSim, so the children * need to be moved outside the tube coupler. @@ -142,19 +211,91 @@ public class RockSimDocumentDTOTest extends RockSimTestBase { Files.delete(output); } - private OpenRocketDocument makeTubeCouplerRocket() { + private OpenRocketDocument makePodsRocket() { OpenRocketDocument document = OpenRocketDocumentFactory.createNewRocket(); Rocket rocket = document.getRocket(); AxialStage stage = rocket.getStage(0); + + // Stage children NoseCone noseCone = new NoseCone(); noseCone.setName("Nose Cone"); stage.addChild(noseCone); BodyTube tube = new BodyTube(); tube.setName("Body Tube"); stage.addChild(tube); + + // Body tube children + PodSet pod1 = new PodSet(); + pod1.setName("Pod 1"); + tube.addChild(pod1); + PodSet pod2 = new PodSet(); + pod2.setName("Pod 2"); + tube.addChild(pod2); + PodSet pod3 = new PodSet(); + pod2.setName("Pod 3"); + tube.addChild(pod3); + + // Pod 1 children + NoseCone noseCone1 = new NoseCone(); + noseCone1.setName("Nose Cone 1"); + pod1.addChild(noseCone1); + + // Pod 2 children + NoseCone noseCone2 = new NoseCone(); + noseCone2.setName("Nose Cone 2"); + pod2.addChild(noseCone2); + BodyTube tube2 = new BodyTube(); + tube2.setName("Body Tube 2"); + pod2.addChild(tube2); + + // Set pod parameters + pod1.setInstanceCount(1); + pod2.setInstanceCount(2); + pod3.setInstanceCount(3); + + pod1.setAxialMethod(AxialMethod.ABSOLUTE); + pod1.setAxialOffset(0.01); + pod2.setAxialMethod(AxialMethod.TOP); + pod2.setAxialOffset(0.02); + pod3.setAxialMethod(AxialMethod.BOTTOM); + pod3.setAxialOffset(0.03); + + pod1.setRadiusMethod(RadiusMethod.RELATIVE); + pod1.setRadiusOffset(0.015); + pod2.setRadiusMethod(RadiusMethod.FREE); + pod2.setRadiusOffset(0.025); + pod3.setRadiusMethod(RadiusMethod.RELATIVE); + pod3.setRadiusOffset(0.035); + + pod1.setAngleOffset(Math.PI); + pod2.setAngleOffset(- Math.PI / 2); + pod3.setAngleOffset(Math.PI / 3); + + return document; + } + + private OpenRocketDocument makeTubeCouplerRocket() { + OpenRocketDocument document = OpenRocketDocumentFactory.createNewRocket(); + Rocket rocket = document.getRocket(); + AxialStage stage = rocket.getStage(0); + + // Stage children + NoseCone noseCone = new NoseCone(); + noseCone.setName("Nose Cone"); + stage.addChild(noseCone); + BodyTube tube = new BodyTube(); + tube.setName("Body Tube"); + stage.addChild(tube); + + // Body tube children TubeCoupler coupler = new TubeCoupler(); coupler.setName("Tube coupler 1"); tube.addChild(coupler); + TubeCoupler coupler3 = new TubeCoupler(); + coupler3.setName("Tube Coupler 3"); + tube.addChild(coupler3); + + // Tube coupler 1 children InnerTube innerTube = new InnerTube(); innerTube.setName("Inner Tube"); coupler.addChild(innerTube); @@ -182,9 +323,8 @@ public class RockSimDocumentDTOTest extends RockSimTestBase { MassComponent massComponent = new MassComponent(); massComponent.setName("Mass Component"); coupler.addChild(massComponent); - TubeCoupler coupler3 = new TubeCoupler(); - coupler3.setName("Tube Coupler 3"); - tube.addChild(coupler3); + + // Tube coupler 3 children Parachute parachute2 = new Parachute(); parachute2.setName("Parachute 2"); coupler3.addChild(parachute2); diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/PodTest.rkt b/core/test/net/sf/openrocket/file/rocksim/importt/PodTest.rkt new file mode 100644 index 000000000..16820a168 --- /dev/null +++ b/core/test/net/sf/openrocket/file/rocksim/importt/PodTest.rkt @@ -0,0 +1,1277 @@ + +4 + + +Pod Test +1 +1 +1 +0.75 +0.8 +0.81 +0.95 +0.95 +1 +0. +0. +0. +0. +0. +0. +0. +0. +4 +914.4 +0 +0 +0 +1 +0 +4 +22 +1 +0 +0,458.248,0,0 +0,19.4182,0,0 +0,412.178,0,0 +0,23.8755,0,0 +0,0,0,0 +0,0,0,0 +0 +1 +0 +1 +0. +0. +0 +0 +0 +0 +0 +0 +0 +0 +0. +10. +10. +10. +10. +0 +0 +0 +10. +0.15 +black +263.6 +78.74 +1062.8 +1062.8 +0. +1062.8 +0,78.74,0,0 +0,1062.8,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +Aerotech +48.194 +1049.21 +Polystyrene PS +Nose cone 1 +152.4 +1 +0. +74.3632 +154. +0.0245355 +0.0245355 +0. +0 +11191 +1.9in Nose Cone +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +8 +1 +0 +0 +blue +2. +0.11224 +2. +0.11224 +1 +0 +8 +0 +0. +241.3 +48.26 +0 +1 +1 +0. +3.18 +0. +0. +0. +0. +0. + + +Custom +0. +0. +Custom +Pod 1 +0. +0 +0. +0. +0. +0. +0. +0. +0 +24.13 +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +9 +1 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +0. +1 +1 +1 + + +Aerospace Speciality Products +0. +1905.24 +G10 phenolic +Fin set 1 +0. +0 +0. +1.53887 +12.7046 +0.00131334 +0.00394002 +0. +0 +FGST20 +G10 Fin Sm Trap (0.016) +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +10 +0 +0 +0 +blue +1.83709 +0.00745107 +4.44342 +0.00745107 +1 +0 +8 +0 +0. +3 +25.4 +15.9 +31.8 +31.8 +4.76 +0.41 +0 +0 +0 +0. +0. +0. +1 +0.149994 +0. +38.8109 +2.56541 +0.625984 +0. +0. + + + + + + +Apogee +30. +0. +Mass object 1 +0. +1 +0. +0. +0. +0. +0. +0. +0 +29623 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +16 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +0. +0 +0. + + + + + + +Apogee +0. +1121.29 +Paper +Body tube 1 +0. +0 +0. +352.264 +125. +0.0392699 +0.0392699 +0. +0 +10131 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +2 +1 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +241.3 +50. +30. +250. +0 +0 +0. +0.5 +0. +0. +0 +0 + + +Custom +0. +0. +Pod 2 +0. +0 +50. +0. +0. +0. +0. +0. +0 +75. +-0.18963 +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +3 +1 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +291.3 +1 +1 +1 + + +Aerotech +141. +924.265 +Polyethylene LDPE +Nose cone 2 +254. +1 +0. +42.8643 +126.345 +0.0211082 +0.0211082 +0. +0 +11401 +4in Nose Cone 4:1 Ogive +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +4 +1 +0 +0 +blue +2. +0.384158 +2. +0.384158 +1 +0 +8 +0 +291.3 +200. +50. +0 +1 +1 +0. +2.36 +0. +0. +0. +0. +0. + + +Apogee +5.3 +0.00112861 +300lb Kevlar (Apogee 29506) +Mass object 2 +0. +1 +0. +0. +0. +0. +0. +0. +2 +29625 +Screw Eye (Large) - For Mid-power rockets +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +17 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +291.3 +0 +0. + + + + + + +Apogee +0. +1121.29 +Paper +Body tube 2 +0. +0 +200. +79.2595 +50. +0.015708 +0.015708 +0. +0 +10064 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +13 +1 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +491.3 +50. +40. +100. +0 +0 +0. +0.5 +0. +0. +0 +0 + + +Aerospace Speciality Products +0. +1905.24 +G10 phenolic +Fin set 2 +0. +0 +0. +1.53887 +12.7046 +0.00131334 +0.00394002 +0. +0 +FGST20 +G10 Fin Sm Trap (0.016) +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +14 +0 +0 +0 +blue +2.64567 +0.498751 +4.49664 +0.498751 +1 +0 +8 +0 +491.3 +3 +25.4 +15.9 +31.8 +31.8 +4.76 +0.41 +0 +0 +0 +0. +0. +0. +1 +0.149994 +0. +39.6809 +2.59614 +0.625984 +0. +0. + + + + +Custom +0. +0. +Custom +Pod 3 +0. +0 +40. +0. +0. +0. +0. +0. +0 +50. +1.5708 +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +18 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +531.3 +1 +0 +0 + + +Apogee +0. +1121.29 +Paper +Body tube 3 +0. +0 +0. +17.3079 +165.1 +0.0310895 +0.0310895 +0. +0 +10110 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +19 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +531.3 +29.97 +28.96 +330.2 +0 +0 +0. +0.5 +0. +0. +0 +0 + + + + + + +Apogee +0. +1121.29 +Paper +Launch lug 1 +0. +0 +0. +2.796 +12.7 +0.00209309 +0.00119695 +0. +0 +13051 +1/8in X 1in Launch Lug +32.5 +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +21 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +491.3 +15. +10. +50. +0 + + + + + + +LOC Precision +0.375 +1049.21 +Polystyrene PS +Transition 2 +120.65 +0 +300. +51.2048 +35.4462 +0.0160582 +0.0160582 +0. +0 +AR-3.90-3.00 +Airframe Reducer from 3.90 to 3.00 +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +15 +0 +0 +0 +blue +6.44046 +0.626578 +6.44046 +0.626578 +1 +0 +8 +0 +591.3 +50. +100. +63.5 +0 +0. +0. +1 +3.18 +0. +0. +0. +0 +0. +127. +63.5 + + + + + + +Aerospace Speciality Products +0. +128.148 +Balsa +Bulkhead 1 +0. +0 +0. +9.23939 +51. +0. +0. +0. +0 +BH60 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +22 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +241.3 +30. +0. +102. +0 +1 +1 + + + + + + +LOC Precision +0.25 +1049.21 +Polystyrene PS +Transition 1 +107.95 +0 +0. +54.365 +41.7981 +0.0170804 +0.0170804 +0. +0 +AR-3.00-2.14 +Airframe Reducer from 3.00 to 2.14 +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +5 +1 +0 +0 +blue +2.49499 +0.532996 +2.49499 +0.532996 +1 +0 +8 +0 +491.3 +57.4 +78.74 +79.25 +0 +0. +0. +1 +3.18 +0. +0. +0. +0 +0. +292.389 +213.139 + + +Apogee +8.5 +0.00112861 +300lb Kevlar (Apogee 29506) +Mass object 3 +0. +1 +0. +0. +0. +0. +0. +0. +2 +29620 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +20 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +491.3 +0 +0. + + + + +Custom +0. +0. +Custom +Pod 4 +0. +0 +0. +0. +0. +0. +0. +0. +0 +63.5 +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +7 +1 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +491.3 +1 +1 +1 + + +Aerotech +48.194 +1049.21 +Polystyrene PS +Nose cone 3 +152.4 +1 +0. +74.3632 +154. +0.0245355 +0.0245355 +0. +0 +11191 +1.9in Nose Cone +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +12 +0 +0 +0 +blue +2. +0.60354 +2. +0.60354 +1 +0 +8 +0 +491.3 +241.3 +48.26 +0 +1 +1 +0. +3.18 +0. +0. +0. +0. +0. + + + + +Apogee +0. +1121.29 +Paper +Body tube 4 +0. +0 +241.3 +17.3079 +165.1 +0.0310895 +0.0310895 +0. +0 +10110 + +0. +0. +file=()|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr=(0)|flips=(0)|flipt=(0)|preventseam=(1)|rotate=(0) +1. +0. +1. +0. +1. +blue +blue +white +1 +11 +0 +0 +0 +blue +0. +0. +0. +0. +1 +0 +8 +0 +732.6 +29.97 +28.96 +330.2 +0 +0 +0. +0.5 +0. +0. +0 +0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/RockSimLoaderTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/RockSimLoaderTest.java index c6d3cbbb8..1eac69a8a 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/RockSimLoaderTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/RockSimLoaderTest.java @@ -8,10 +8,17 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import net.sf.openrocket.rocketcomponent.Bulkhead; +import net.sf.openrocket.rocketcomponent.CenteringRing; import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.MassComponent; import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import org.junit.Assert; @@ -212,7 +219,41 @@ public class RockSimLoaderTest extends BaseTestCase { Assert.assertEquals(0.185d, stage1.getOverrideMass(), 0.001); Assert.assertTrue(stage1.isCGOverridden()); Assert.assertEquals(0.3d, stage1.getOverrideCG().x, 0.001); - Assert.assertEquals(3, loader.getWarnings().size()); + Assert.assertEquals(2, loader.getWarnings().size()); + + NoseCone nc = (NoseCone) stage1.getChild(0); + Assert.assertEquals(2, nc.getChildCount()); + Assert.assertEquals("Clay", nc.getChild(0).getName()); + RocketComponent it = nc.getChild(1); + Assert.assertEquals(InnerTube.class, it.getClass()); + Assert.assertEquals("Attachment Rod", it.getName()); + + Assert.assertEquals(3, it.getChildCount()); + RocketComponent c = it.getChild(0); + Assert.assertEquals(CenteringRing.class, c.getClass()); + Assert.assertEquals("Plate", c.getName()); + c = it.getChild(1); + Assert.assertEquals(CenteringRing.class, c.getClass()); + Assert.assertEquals("Sleeve ", c.getName()); + c = it.getChild(2); + Assert.assertEquals(Parachute.class, c.getClass()); + Assert.assertEquals("Nose Cone Parachute", c.getName()); + + BodyTube bt1 = (BodyTube) stage1.getChild(1); + Assert.assertEquals(5, bt1.getChildCount()); + Assert.assertEquals("Centering ring", bt1.getChild(0).getName()); + Assert.assertEquals("Centering ring", bt1.getChild(1).getName()); + c = bt1.getChild(2); + Assert.assertEquals(InnerTube.class, c.getClass()); + Assert.assertEquals("Body tube", c.getName()); + Assert.assertEquals("Launch lug", bt1.getChild(3).getName()); + Assert.assertEquals("Pod", bt1.getChild(4).getName()); + + PodSet pod = (PodSet) bt1.getChild(4); + Assert.assertEquals(1, pod.getChildCount()); + c = pod.getChild(0); + Assert.assertEquals(BodyTube.class, c.getClass()); + Assert.assertEquals("Body tube", pod.getChild(0).getName()); Assert.assertEquals(1, stage2.getChildCount()); Assert.assertEquals("2nd Stage Tube", stage2.getChild(0).getName()); @@ -221,8 +262,8 @@ public class RockSimLoaderTest extends BaseTestCase { Assert.assertTrue(stage2.isCGOverridden()); Assert.assertEquals(0.4d, stage2.getOverrideCG().x, 0.001); - BodyTube bt = (BodyTube) stage2.getChild(0); - LaunchLug ll = (LaunchLug) bt.getChild(6); + BodyTube bt2 = (BodyTube) stage2.getChild(0); + LaunchLug ll = (LaunchLug) bt2.getChild(6); Assert.assertEquals(1.22d, ll.getAngleOffset(), 0.001); Assert.assertEquals(2, stage3.getChildCount()); @@ -342,6 +383,97 @@ public class RockSimLoaderTest extends BaseTestCase { Assert.assertEquals(0, bodyTube3.getChildCount()); } + @Test + public void testPodRocket() throws IOException, RocketLoadException{ + RockSimLoader loader = new RockSimLoader(); + OpenRocketDocument doc = loadRockSimRocket(loader, "PodTest.rkt"); + + Assert.assertNotNull(doc); + Rocket rocket = doc.getRocket(); + Assert.assertNotNull(rocket); + Assert.assertEquals("Pod Test", doc.getRocket().getName()); + Assert.assertEquals(3, loader.getWarnings().size()); + + InputStream stream = this.getClass().getResourceAsStream("PodTest.rkt"); + Assert.assertNotNull("Could not open PodTest.rkt", stream); + + doc = OpenRocketDocumentFactory.createEmptyRocket(); + DocumentLoadingContext context = new DocumentLoadingContext(); + context.setOpenRocketDocument(doc); + context.setMotorFinder(new DatabaseMotorFinder()); + loader.loadFromStream(context, new BufferedInputStream(stream)); + + Assert.assertNotNull(doc); + rocket = doc.getRocket(); + Assert.assertNotNull(rocket); + Assert.assertEquals(1, rocket.getStageCount()); + AxialStage stage1 = (AxialStage) rocket.getChild(0); + + Assert.assertEquals(3, stage1.getChildCount()); + RocketComponent noseCone1 = stage1.getChild(0); + RocketComponent bodyTube1 = stage1.getChild(1); + RocketComponent transition1 = stage1.getChild(2); + Assert.assertEquals(NoseCone.class, noseCone1.getClass()); + Assert.assertEquals(BodyTube.class, bodyTube1.getClass()); + Assert.assertEquals(Transition.class, transition1.getClass()); + Assert.assertEquals("Nose cone 1", noseCone1.getName()); + Assert.assertEquals("Body tube 1", bodyTube1.getName()); + Assert.assertEquals("Transition 1", transition1.getName()); + + Assert.assertEquals(1, noseCone1.getChildCount()); + RocketComponent component = noseCone1.getChild(0); + Assert.assertEquals(MassComponent.class, component.getClass()); + Assert.assertEquals("Mass object 1", component.getName()); + + Assert.assertEquals(2, bodyTube1.getChildCount()); + RocketComponent pod2 = bodyTube1.getChild(0); + Assert.assertEquals(PodSet.class, pod2.getClass()); + Assert.assertEquals("Pod 2", pod2.getName()); + component = bodyTube1.getChild(1); + Assert.assertEquals(Bulkhead.class, component.getClass()); + Assert.assertEquals("Bulkhead 1", component.getName()); + + Assert.assertEquals(3, pod2.getChildCount()); + RocketComponent noseCone2 = pod2.getChild(0); + Assert.assertEquals(NoseCone.class, noseCone2.getClass()); + Assert.assertEquals("Nose cone 2", noseCone2.getName()); + RocketComponent bodyTube2 = pod2.getChild(1); + Assert.assertEquals(BodyTube.class, bodyTube2.getClass()); + Assert.assertEquals("Body tube 2", bodyTube2.getName()); + component = pod2.getChild(2); + Assert.assertEquals(Transition.class, component.getClass()); + Assert.assertEquals("Transition 2", component.getName()); + + Assert.assertEquals(1, noseCone2.getChildCount()); + component = noseCone2.getChild(0); + Assert.assertEquals(MassComponent.class, component.getClass()); + Assert.assertEquals("Mass object 2", component.getName()); + + Assert.assertEquals(3, bodyTube2.getChildCount()); + component = bodyTube2.getChild(0); + Assert.assertEquals(TrapezoidFinSet.class, component.getClass()); + Assert.assertEquals("Fin set 2", component.getName()); + RocketComponent pod3 = bodyTube2.getChild(1); + Assert.assertEquals(PodSet.class, pod3.getClass()); + Assert.assertEquals("Pod 3", pod3.getName()); + component = bodyTube2.getChild(2); + Assert.assertEquals(LaunchLug.class, component.getClass()); + Assert.assertEquals("Launch lug 1", component.getName()); + + Assert.assertEquals(1, pod3.getChildCount()); + component = pod3.getChild(0); + Assert.assertEquals(BodyTube.class, component.getClass()); + Assert.assertEquals("Body tube 3", component.getName()); + Assert.assertEquals(0.04, pod3.getAxialOffset(), MathUtil.EPSILON); + Assert.assertEquals(Math.PI / 2, pod3.getAngleOffset(), 0.0001); + Assert.assertEquals(0.05, pod3.getRadiusOffset(), MathUtil.EPSILON); + + Assert.assertEquals(1, transition1.getChildCount()); + component = transition1.getChild(0); + Assert.assertEquals(MassComponent.class, component.getClass()); + Assert.assertEquals("Mass object 3", component.getName()); + } + public static OpenRocketDocument loadRockSimRocket(RockSimLoader theLoader, String fileName) throws IOException, RocketLoadException { try (InputStream stream = RockSimLoaderTest.class.getResourceAsStream(fileName)) { Assert.assertNotNull("Could not open " + fileName, stream); diff --git a/core/test/net/sf/openrocket/simulation/DisableStageTest.java b/core/test/net/sf/openrocket/simulation/DisableStageTest.java index 1162a7ef0..bbd62d4cf 100644 --- a/core/test/net/sf/openrocket/simulation/DisableStageTest.java +++ b/core/test/net/sf/openrocket/simulation/DisableStageTest.java @@ -6,8 +6,6 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.exception.MotorIgnitionException; import net.sf.openrocket.simulation.exception.SimulationException; -import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; -import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import net.sf.openrocket.util.TestRockets; import org.junit.Assert; @@ -35,11 +33,9 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - // Since there are no stages, the simulation should throw an exception. try { - simDisabled.simulate(simulationListener); + simDisabled.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation should have thrown a MotorIgnitionException"); @@ -56,7 +52,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); // Re-enable all stages. - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -83,9 +79,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - - compareSims(simRemoved, simDisabled, simulationListener, delta); + compareSims(simRemoved, simDisabled, delta); //// Test re-enableing the stage. Rocket rocketOriginal = TestRockets.makeBeta(); @@ -96,7 +90,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -173,9 +167,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - - compareSims(simRemoved, simDisabled, simulationListener, delta); + compareSims(simRemoved, simDisabled, delta); //// Test re-enableing the stage. Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); @@ -186,7 +178,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -214,11 +206,9 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - // There should be no motors left at this point, so a no motors exception should be thrown try { - simRemoved.simulate(simulationListener); + simRemoved.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation failed: " + e); @@ -226,7 +216,7 @@ public class DisableStageTest extends BaseTestCase { } try { - simDisabled.simulate(simulationListener); + simDisabled.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation failed: " + e); @@ -242,7 +232,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -259,13 +249,11 @@ public class DisableStageTest extends BaseTestCase { * - groundHitVelocity * @param simExpected the expected simulation results * @param simActual the actual simulation results - * @param simulationListener the simulation listener to use for the comparison * @param delta the error margin for the comparison (e.g. 0.05 = 5 % error margin) */ - private void compareSims(Simulation simExpected, Simulation simActual, - SimulationListener simulationListener, double delta) { + private void compareSims(Simulation simExpected, Simulation simActual, double delta) { try { - simExpected.simulate(simulationListener); + simExpected.simulate(); double maxAltitudeOriginal = simExpected.getSimulatedData().getMaxAltitude(); double maxVelocityOriginal = simExpected.getSimulatedData().getMaxVelocity(); double maxMachNumberOriginal = simExpected.getSimulatedData().getMaxMachNumber(); @@ -274,7 +262,7 @@ public class DisableStageTest extends BaseTestCase { double launchRodVelocityOriginal = simExpected.getSimulatedData().getLaunchRodVelocity(); double deploymentVelocityOriginal = simExpected.getSimulatedData().getDeploymentVelocity(); - simActual.simulate(simulationListener); + simActual.simulate(); double maxAltitudeDisabled = simActual.getSimulatedData().getMaxAltitude(); double maxVelocityDisabled = simActual.getSimulatedData().getMaxVelocity(); double maxMachNumberDisabled = simActual.getSimulatedData().getMaxMachNumber(); diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java new file mode 100644 index 000000000..2b45608d8 --- /dev/null +++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java @@ -0,0 +1,107 @@ +package net.sf.openrocket.simulation; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; +import net.sf.openrocket.util.TestRockets; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Tests to verify that simulations contain all the expected flight events. + */ +public class FlightEventsTest extends BaseTestCase { + /** + * Tests for a single stage design. + */ + @Test + public void testSingleStage() throws SimulationException { + Rocket rocket = TestRockets.makeEstesAlphaIII(); + Simulation sim = new Simulation(rocket); + sim.getOptions().setISAAtmosphere(true); + sim.getOptions().setTimeStep(0.05); + sim.setFlightConfigurationId(TestRockets.TEST_FCID_0); + + sim.simulate(); + + // Test branch count + int branchCount = sim.getSimulatedData().getBranchCount(); + assertEquals(" Single stage simulation invalid branch count", 1, branchCount); + + FlightEvent.Type[] expectedEventTypes = {FlightEvent.Type.LAUNCH, FlightEvent.Type.IGNITION, FlightEvent.Type.LIFTOFF, + FlightEvent.Type.LAUNCHROD, FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, + FlightEvent.Type.APOGEE, FlightEvent.Type.GROUND_HIT, FlightEvent.Type.SIMULATION_END}; + + // Test event count + FlightDataBranch branch = sim.getSimulatedData().getBranch(0); + List eventList = branch.getEvents(); + List eventTypes = eventList.stream().map(FlightEvent::getType).collect(Collectors.toList()); + assertEquals(" Single stage simulation invalid number of events", expectedEventTypes.length, eventTypes.size()); + + // Test that all expected events are present, and in the right order + for (int i = 0; i < expectedEventTypes.length; i++) { + assertSame(" Flight type " + expectedEventTypes[i] + " not found in single stage simulation", + eventTypes.get(i), expectedEventTypes[i]); + } + } + + /** + * Tests for a multi-stage design. + */ + @Test + public void testMultiStage() throws SimulationException { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + Simulation sim = new Simulation(rocket); + sim.getOptions().setISAAtmosphere(true); + sim.getOptions().setTimeStep(0.05); + rocket.getSelectedConfiguration().setAllStages(); + FlightConfigurationId fcid = rocket.getSelectedConfiguration().getFlightConfigurationID(); + sim.setFlightConfigurationId(fcid); + + sim.simulate(); + + // Test branch count + int branchCount = sim.getSimulatedData().getBranchCount(); + assertEquals(" Multi-stage simulation invalid branch count", 3, branchCount); + + for (int b = 0; b < 3; b++) { + FlightEvent.Type[] expectedEventTypes; + switch (b) { + case 0: + expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.LAUNCH, FlightEvent.Type.IGNITION, FlightEvent.Type.IGNITION, + FlightEvent.Type.LIFTOFF, FlightEvent.Type.LAUNCHROD, FlightEvent.Type.APOGEE, + FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.STAGE_SEPARATION, + FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.STAGE_SEPARATION, + FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT, FlightEvent.Type.SIMULATION_END}; + break; + case 1: + case 2: + expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT, + FlightEvent.Type.SIMULATION_END}; + break; + default: + throw new IllegalStateException("Invalid branch number " + b); + } + + // Test event count + FlightDataBranch branch = sim.getSimulatedData().getBranch(b); + List eventList = branch.getEvents(); + List eventTypes = eventList.stream().map(FlightEvent::getType).collect(Collectors.toList()); + assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events", expectedEventTypes.length, eventTypes.size()); + + // Test that all expected events are present, and in the right order + for (int i = 0; i < expectedEventTypes.length; i++) { + assertSame(" Flight type " + expectedEventTypes[i] + ", branch " + b + " not found in multi-stage simulation", + eventTypes.get(i), expectedEventTypes[i]); + } + } + } + +} diff --git a/swing/resources/datafiles/examples/TARC Payloader.ork b/swing/resources/datafiles/examples/TARC Payloader.ork index a62d4a8a1..0482ad09a 100644 Binary files a/swing/resources/datafiles/examples/TARC Payloader.ork and b/swing/resources/datafiles/examples/TARC Payloader.ork differ diff --git a/swing/src/net/sf/openrocket/gui/adaptors/IntegerModel.java b/swing/src/net/sf/openrocket/gui/adaptors/IntegerModel.java index 4703642d3..6a32355f9 100644 --- a/swing/src/net/sf/openrocket/gui/adaptors/IntegerModel.java +++ b/swing/src/net/sf/openrocket/gui/adaptors/IntegerModel.java @@ -13,6 +13,7 @@ import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import net.sf.openrocket.util.MathUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -210,9 +211,16 @@ public class IntegerModel implements StateChangeListener { * Sets the value of the variable. */ public void setValue(int v) { + int clampedValue = MathUtil.clamp(v, minValue, maxValue); + if (clampedValue != v) { + log.debug("Clamped value " + v + " to " + clampedValue + " for " + this); + v = clampedValue; + } + log.debug("Setting value " + v + " for " + this); try { setMethod.invoke(source, v); + fireStateChanged(); } catch (IllegalArgumentException e) { throw new BugException(e); } catch (IllegalAccessException e) { diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java index f6554f544..b4aebd2be 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java @@ -86,7 +86,7 @@ public class ComponentAssemblyConfig extends RocketComponentConfig { // set angle JLabel angleLabel = new JLabel(trans.get("StageConfig.parallel.angle")); motherPanel.add( angleLabel, "align left"); - DoubleModel angleModel = new DoubleModel( boosters, "AngleOffset", 1.0, UnitGroup.UNITS_ANGLE, 0.0, Math.PI*2); + DoubleModel angleModel = new DoubleModel( boosters, "AngleOffset", 1.0, UnitGroup.UNITS_ANGLE, -Math.PI, Math.PI); JSpinner angleSpinner = new JSpinner(angleModel.getSpinnerModel()); angleSpinner.setEditor(new SpinnerEditor(angleSpinner)); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index bbcf8f8d3..b0b120c82 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -199,6 +199,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe @Override public Object getValueAt(int row) { Object c = stabData.get(row).name; + return c.toString(); } @@ -237,7 +238,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe @Override public Object getValueAt(int row) { - return NOUNIT.toString(stabData.get(row).cpx); + return unit.toString(stabData.get(row).cpx); } }, new Column("CN" + ALPHA + "") { @@ -541,11 +542,6 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe rollData.clear(); for(final RocketComponent comp: configuration.getAllComponents()) { - // // this is actually redundant, because the analysis will not contain inactive stages. - // if (!configuration.isComponentActive(comp)) { - // continue; - // } - CMAnalysisEntry cmEntry = cmMap.get(comp.hashCode()); if (null == cmEntry) { log.warn("Could not find massData entry for component: " + comp.getName()); @@ -556,7 +552,11 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe continue; } - LongitudinalStabilityRow row = new LongitudinalStabilityRow(cmEntry.name, cmEntry.source); + String name = cmEntry.name; + if (cmEntry.source instanceof Rocket) { + name = trans.get("componentanalysisdlg.TOTAL"); + } + LongitudinalStabilityRow row = new LongitudinalStabilityRow(name, cmEntry.source); stabData.add(row); row.source = cmEntry.source; diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 9f702dd22..04c657986 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -504,8 +504,7 @@ public class SimulationPanel extends JPanel { private void openDialog(final Simulation sim) { boolean plotMode = false; - if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.LOADED - || sim.getStatus() == Status.EXTERNAL)) { + if (sim.hasSimulationData() && Simulation.isStatusUpToDate(sim.getStatus())) { plotMode = true; } openDialog(plotMode, sim); diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java index 9900305b4..da0a95dd1 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java @@ -60,7 +60,7 @@ import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; /* - * It should be possible to simplify this code quite a bit by using a single Renderer instance for + * TODO: It should be possible to simplify this code quite a bit by using a single Renderer instance for * both datasets and the legend. But for now, the renderers are queried for the line color information * and this is held in the Legend. */ diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index bda631bc3..567495c0c 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -857,8 +857,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change // Re-run the present simulation(s) List sims = new LinkedList<>(); for (Simulation sim : document.getSimulations()) { - if (sim.getStatus() == Simulation.Status.UPTODATE || sim.getStatus() == Simulation.Status.LOADED - || !document.getRocket().getFlightConfiguration(sim.getFlightConfigurationId()).hasMotors()) + if (Simulation.isStatusUpToDate(sim.getStatus()) || + !document.getRocket().getFlightConfiguration(sim.getFlightConfigurationId()).hasMotors()) continue; // Find a Simulation based on the current flight configuration diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java index 03b9ab944..7955bdae3 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java @@ -292,8 +292,7 @@ public class SimulationEditDialog extends JDialog { @Override public void actionPerformed(ActionEvent e) { // If the simulation is out of date, run the simulation. - if (simulationList[0].getStatus() != Simulation.Status.UPTODATE && - simulationList[0].getStatus() != Simulation.Status.LOADED) { + if (!Simulation.isStatusUpToDate(simulationList[0].getStatus())) { new SimulationRunDialog(SimulationEditDialog.this.parentWindow, document, simulationList[0]).setVisible(true); }