diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index d51448699..fbdac87e4 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -68,5 +68,5 @@ public interface AerodynamicCalculator extends Monitorable { */ public AerodynamicCalculator newInstance(); - public boolean isContinuous( final Rocket rkt); + public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt); } diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index b1d2f3bfc..add6b3b7b 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -4,6 +4,7 @@ import static net.sf.openrocket.util.MathUtil.pow2; import java.util.*; +import net.sf.openrocket.rocketcomponent.AxialStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +81,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { Map assemblyMap = new LinkedHashMap<>(); // Calculate non-axial force data - calculateForceAnalysis(conditions, configuration.getRocket(), instMap, eachMap, assemblyMap, warnings); + calculateForceAnalysis(configuration, conditions, configuration.getRocket(), instMap, eachMap, assemblyMap, warnings); // Calculate drag coefficient data AerodynamicForces rocketForces = assemblyMap.get(configuration.getRocket()); @@ -125,7 +126,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { return finalMap; } - private AerodynamicForces calculateForceAnalysis( FlightConditions conds, + private AerodynamicForces calculateForceAnalysis( FlightConfiguration configuration, + FlightConditions conds, RocketComponent comp, InstanceMap instances, Map eachForces, @@ -152,8 +154,12 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { } for( RocketComponent child : comp.getChildren()) { + // Ignore inactive stages + if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { + continue; + } // forces particular to each component - AerodynamicForces childForces = calculateForceAnalysis(conds, child, instances, eachForces, assemblyForces, warnings); + AerodynamicForces childForces = calculateForceAnalysis(configuration, conds, child, instances, eachForces, assemblyForces, warnings); if(null != childForces) { aggregateForces.merge(childForces); @@ -240,7 +246,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { if (calcMap == null) buildCalcMap(configuration); - if( ! isContinuous( configuration.getRocket() ) ){ + if (!isContinuous(configuration, configuration.getRocket())){ warnings.add( Warning.DIAMETER_DISCONTINUITY); } @@ -266,20 +272,32 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { } @Override - public boolean isContinuous( final Rocket rkt){ - return testIsContinuous( rkt); + public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt){ + return testIsContinuous(configuration, rkt); } - private boolean testIsContinuous( final RocketComponent treeRoot ){ + private boolean testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot ){ Queue queue = new LinkedList<>(); - queue.addAll(treeRoot.getChildren()); + for (RocketComponent child : treeRoot.getChildren()) { + // Ignore inactive stages + if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { + continue; + } + queue.add(child); + } boolean isContinuous = true; SymmetricComponent prevComp = null; while((isContinuous)&&( null != queue.peek())){ RocketComponent comp = queue.poll(); if( comp instanceof SymmetricComponent ){ - queue.addAll( comp.getChildren()); + for (RocketComponent child : comp.getChildren()) { + // Ignore inactive stages + if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { + continue; + } + queue.add(child); + } SymmetricComponent sym = (SymmetricComponent) comp; if( null == prevComp){ @@ -303,7 +321,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { prevComp = sym; }else if( comp instanceof ComponentAssembly ){ - isContinuous &= testIsContinuous( comp ); + isContinuous &= testIsContinuous(configuration, comp); } } @@ -319,7 +337,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { * @param configuration Rocket configuration * @param conditions Flight conditions taken into account * @param map ? - * @param set Set to handle + * @param warningSet Set to handle warnings * @return friction drag for entire rocket */ private double calculateFrictionCD(FlightConfiguration configuration, FlightConditions conditions, @@ -611,7 +629,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { double radius = 0; final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent(); - if (prevComponent != null) + if (prevComponent != null && configuration.isComponentActive(prevComponent)) radius = prevComponent.getAftRadius(); if (radius < s.getForeRadius()) { @@ -672,7 +690,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { // its aft CD double radius = 0; final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent(); - if (prevComponent != null) { + if (prevComponent != null && configuration.isComponentActive(prevComponent)) { radius = prevComponent.getAftRadius(); } diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 463ed7f4b..4b36d03dc 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -104,7 +104,7 @@ public class MassCalculator implements Monitorable { public static RigidBody calculate( final MassCalculation.Type _type, final SimulationStatus status ){ final FlightConfiguration config = status.getConfiguration(); final double time = status.getSimulationTime(); - final Collection activeMotorList = status.getMotors(); + final Collection activeMotorList = status.getActiveMotors(); MassCalculation calculation= new MassCalculation( _type, config, time, activeMotorList, config.getRocket(), Transformation.IDENTITY, null); calculation.calculateAssembly(); diff --git a/core/src/net/sf/openrocket/motor/IgnitionEvent.java b/core/src/net/sf/openrocket/motor/IgnitionEvent.java index 60a2bf2ea..619642167 100644 --- a/core/src/net/sf/openrocket/motor/IgnitionEvent.java +++ b/core/src/net/sf/openrocket/motor/IgnitionEvent.java @@ -4,6 +4,7 @@ import java.util.Locale; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.startup.Application; @@ -13,25 +14,25 @@ public enum IgnitionEvent { //// Automatic (launch or ejection charge) AUTOMATIC( "AUTOMATIC", "MotorMount.IgnitionEvent.AUTOMATIC"){ @Override - public boolean isActivationEvent(FlightEvent testEvent, RocketComponent targetComponent) { + public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent) { AxialStage targetStage = targetComponent.getStage(); - if ( targetStage.isLaunchStage() ){ - return LAUNCH.isActivationEvent(testEvent, targetComponent); + if (targetStage.isLaunchStage(config)) { + return LAUNCH.isActivationEvent(config, testEvent, targetComponent); } else { - return EJECTION_CHARGE.isActivationEvent(testEvent, targetComponent); + return EJECTION_CHARGE.isActivationEvent(config, testEvent, targetComponent); } } }, LAUNCH ( "LAUNCH", "MotorMount.IgnitionEvent.LAUNCH"){ @Override - public boolean isActivationEvent( FlightEvent fe, RocketComponent source){ + public boolean isActivationEvent(FlightConfiguration config, FlightEvent fe, RocketComponent source){ return (fe.getType() == FlightEvent.Type.LAUNCH); } }, EJECTION_CHARGE ("EJECTION_CHARGE", "MotorMount.IgnitionEvent.EJECTION_CHARGE"){ @Override - public boolean isActivationEvent( FlightEvent testEvent, RocketComponent targetComponent){ + public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent){ if (testEvent.getType() != FlightEvent.Type.EJECTION_CHARGE){ return false; } @@ -44,7 +45,7 @@ public enum IgnitionEvent { }, BURNOUT ("BURNOUT", "MotorMount.IgnitionEvent.BURNOUT"){ @Override - public boolean isActivationEvent( FlightEvent testEvent, RocketComponent targetComponent){ + public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent){ if (testEvent.getType() != FlightEvent.Type.BURNOUT) return false; @@ -64,7 +65,7 @@ public enum IgnitionEvent { //public static final IgnitionEvent[] events = {AUTOMATIC, LAUNCH, EJECTION_CHARGE, BURNOUT, NEVER}; - public boolean isActivationEvent( FlightEvent fe, RocketComponent source){ + public boolean isActivationEvent(FlightConfiguration config, FlightEvent fe, RocketComponent source){ // default behavior. Also for the NEVER case. return false; } diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java index 20f3a13f5..8e0f533c5 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java @@ -67,6 +67,23 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC public boolean isCompatible(Class type) { return BodyComponent.class.isAssignableFrom(type); } + + /** + * Returns whether the current stage is active in the currently selected configuration. + * @return true if the stage is active, false if not + */ + public boolean isStageActive() { + return getRocket().getSelectedConfiguration().isStageActive(getStageNumber()); + } + + /** + * Returns whether the current stage is active in the flight configuration. + * @param fc the flight configuration to check + * @return true if the stage is active, false if not + */ + public boolean isStageActive(FlightConfiguration fc) { + return fc.isStageActive(getStageNumber()); + } @Override public void copyFlightConfiguration(FlightConfigurationId oldConfigId, FlightConfigurationId newConfigId) { @@ -113,11 +130,11 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC /** * returns if the object is a launch stage + * @param config the flight configuration which will check which stages are active * @return if the object is a launch stage */ - public boolean isLaunchStage(){ - return ( this instanceof ParallelStage ) - ||( getRocket().getBottomCoreStage().equals(this)); + public boolean isLaunchStage(FlightConfiguration config) { + return (getRocket().getBottomCoreStage(config).equals(this)); } /** diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 2ae0211fa..7bb3cfae3 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -60,8 +60,8 @@ public class FlightConfiguration implements FlightConfigurableParameter stages = new HashMap(); - final protected HashMap motors = new HashMap(); + final protected Map stages = new HashMap(); // Map of stage number to StageFlags of the corresponding stage + final protected Map motors = new HashMap(); final private Collection activeMotors = new ArrayList(); final private InstanceMap activeInstances = new InstanceMap(); @@ -179,31 +179,52 @@ public class FlightConfiguration implements FlightConfigurableParameterfalse) or active (true) + * @param activateSubStages whether the sub-stages of the specified stage should be activated as well. */ - private void _setStageActive(final int stageNumber, final boolean _active ) { + public void _setStageActive(final int stageNumber, final boolean _active, final boolean activateSubStages) { if ((0 <= stageNumber) && (stages.containsKey(stageNumber))) { stages.get(stageNumber).active = _active; + if (activateSubStages) { + // Set the active state of all the sub-stages as well. + for (AxialStage stage : rocket.getStage(stageNumber).getSubStages()) { + stages.get(stage.getStageNumber()).active = _active; + } + } fireChangeEvent(); return; } log.error("error: attempt to retrieve via a bad stage number: " + stageNumber); } + /** + * This method flags the specified stage as requested. Actives the sub-stages of the specified stage as well. + * + * @param stageNumber stage number to flag + * @param _active inactive (false) or active (true) + */ + public void _setStageActive(final int stageNumber, final boolean _active ) { + _setStageActive(stageNumber, _active, true); + } + public void toggleStage(final int stageNumber) { if ((0 <= stageNumber) && (stages.containsKey(stageNumber))) { StageFlags flags = stages.get(stageNumber); flags.active = !flags.active; + // Set the active state of all the sub-stages as well. + for (AxialStage stage : rocket.getStage(stageNumber).getSubStages()) { + stages.get(stage.getStageNumber()).active = flags.active; + } updateMotors(); updateActiveInstances(); @@ -338,6 +359,18 @@ public class FlightConfiguration implements FlightConfigurableParameter getAllStages() { + List stages = new ArrayList<>(); + for (StageFlags flags : this.stages.values()) { + stages.add( rocket.getStage(flags.stageNumber)); + } + return stages; + } public List getActiveStages() { List activeStages = new ArrayList<>(); diff --git a/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java b/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java index 8f74ee6fa..d09b71f54 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java @@ -111,8 +111,8 @@ public class ParallelStage extends AxialStage implements FlightConfigurableCompo } @Override - public boolean isLaunchStage(){ - return true; + public boolean isLaunchStage(FlightConfiguration config) { + return config.isStageActive(this.stageNumber); } @Override diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 7a3f1780a..5411440f7 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -197,24 +197,37 @@ public class Rocket extends ComponentAssembly { public AxialStage getStage( final int stageNumber ) { return this.stageMap.get( stageNumber); } - - /* - * Returns the stage at the top of the central stack - * - * @Return a reference to the topmost stage + + /** + * Get the topmost stage, only taking into account active stages from the flight configuration. + * @param config flight configuration dictating which stages are active + * @return the topmost active stage, or null if there are no active stages. */ - public AxialStage getTopmostStage(){ - return (AxialStage) getChild(0); + public AxialStage getTopmostStage(FlightConfiguration config) { + if (config == null) return null; + + for (int i = 0; i < getChildCount(); i++) { + if (getChild(i) instanceof AxialStage && config.isStageActive(getChild(i).getStageNumber())) { + return (AxialStage) getChild(i); + } + } + return null; } - - /* - * Returns the stage at the top of the central stack - * - * @Return a reference to the topmost stage + + /** + * Get the bottommost stage, only taking into account active stages from the flight configuration. + * @param config flight configuration dictating which stages are active + * @return the bottommost active stage, or null if there are no active stages. */ - /*package-local*/ AxialStage getBottomCoreStage(){ - // get last stage that's a direct child of the rocket. - return (AxialStage) children.get( children.size()-1 ); + public AxialStage getBottomCoreStage(FlightConfiguration config) { + if (config == null) return null; + + for (int i = getChildCount() - 1; i >= 0; i--) { + if (getChild(i) instanceof AxialStage && config.isStageActive(getChild(i).getStageNumber())) { + return (AxialStage) getChild(i); + } + } + return null; } @Override @@ -735,6 +748,14 @@ public class Rocket extends ComponentAssembly { fireComponentChangeEvent(ComponentChangeEvent.TREE_CHANGE); return nextConfig.getFlightConfigurationID(); } + + /** + * Return all the flight configurations of this rocket. + * @return all the flight configurations of this rocket. + */ + public FlightConfigurableParameterSet getFlightConfigurations() { + return this.configSet; + } /** diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 076211716..23a954e70 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1545,6 +1545,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab AxialStage stage = (AxialStage) component; this.getRocket().forgetStage(stage); } + + // Remove sub-stages of the removed component + for (AxialStage stage : component.getSubStages()) { + this.getRocket().forgetStage(stage); + } this.checkComponentStructure(); component.checkComponentStructure(); @@ -1749,6 +1754,21 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } throw new IllegalStateException("getStage() called on hierarchy without an AxialStage."); } + + /** + * Returns all the stages that are a child or sub-child of this component. + * @return all the stages that are a child or sub-child of this component. + */ + public final List getSubStages() { + List result = new LinkedList<>(); + Iterator it = iterator(false); + while (it.hasNext()) { + RocketComponent c = it.next(); + if (c instanceof AxialStage) + result.add((AxialStage) c); + } + return result; + } /** * Return the first component assembly component that this component belongs to. diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 8fdae800a..1d5b62e76 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -45,7 +45,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { private final static double AOA_TUMBLE_CONDITION = Math.PI / 9.0; // The thrust must be below this value for the transition to tumbling. - // TODO: this is an arbitrary value + // TODO HIGH: this is an arbitrary value private final static double THRUST_TUMBLE_CONDITION = 0.01; private SimulationStepper currentStepper; @@ -65,7 +65,9 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Set up rocket configuration this.fcid = simulationConditions.getFlightConfigurationID(); - FlightConfiguration simulationConfig = simulationConditions.getRocket().getFlightConfiguration( this.fcid).clone(); + FlightConfiguration origConfig = simulationConditions.getRocket().getFlightConfiguration(this.fcid); + FlightConfiguration simulationConfig = origConfig.clone(); + simulationConfig.copyStages(origConfig); // Clone the stage activation configuration if ( ! simulationConfig.hasMotors() ) { throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined")); } @@ -74,7 +76,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket())); { // main simulation branch - final String branchName = simulationConfig.getRocket().getTopmostStage().getName(); + final String branchName = simulationConfig.getRocket().getTopmostStage(currentStatus.getConfiguration()).getName(); currentStatus.setFlightData(new FlightDataBranch( branchName, FlightDataType.TYPE_TIME)); } toSimulate.push(currentStatus); @@ -273,9 +275,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Check for motor ignition events, add ignition events to queue for (MotorClusterState state : currentStatus.getActiveMotors() ){ - if( state.testForIgnition(event )){ - final double simulationTime = currentStatus.getSimulationTime() ; - + if (state.testForIgnition(currentStatus.getConfiguration(), event)) { MotorClusterState sourceState = (MotorClusterState) event.getData(); double ignitionDelay = 0; if (event.getType() == FlightEvent.Type.BURNOUT) @@ -543,7 +543,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { } - // TODO : FUTURE : do not hard code the 1200 (maybe even make it configurable by the user) + // TODO FUTURE : do not hard code the 1200 (maybe even make it configurable by the user) if( 1200 < currentStatus.getSimulationTime() ){ ret = false; log.error("Simulation hit max time (1200s): aborting."); @@ -553,6 +553,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { // If no motor has ignited, abort if (!currentStatus.isMotorIgnited()) { + // TODO MEDIUM: display this as a warning to the user (e.g. highlight the cell in the simulation panel in red and a hover: 'make sure the motor ignition is correct' or something) throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noIgnition")); } diff --git a/core/src/net/sf/openrocket/simulation/FlightEvent.java b/core/src/net/sf/openrocket/simulation/FlightEvent.java index 55ceaab36..b78ad1f3b 100644 --- a/core/src/net/sf/openrocket/simulation/FlightEvent.java +++ b/core/src/net/sf/openrocket/simulation/FlightEvent.java @@ -172,7 +172,7 @@ public class FlightEvent implements Comparable { * @return */ public void validate(){ - if( this.time == Double.NaN ){ + if(Double.isNaN(this.time)){ throw new IllegalStateException(type.name()+" event has a NaN time!"); } switch( this.type ){ diff --git a/core/src/net/sf/openrocket/simulation/MotorClusterState.java b/core/src/net/sf/openrocket/simulation/MotorClusterState.java index 43c73f9c3..728287c96 100644 --- a/core/src/net/sf/openrocket/simulation/MotorClusterState.java +++ b/core/src/net/sf/openrocket/simulation/MotorClusterState.java @@ -4,6 +4,7 @@ import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationId; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -121,8 +122,8 @@ public class MotorClusterState { /** * Compute the average thrust over an interval. * - * @param simulationTime - * @param cond + * @param startSimulationTime start time of the averaging interval + * @param endSimulationTime end time of the averaging interval * @return */ public double getAverageThrust( final double startSimulationTime, final double endSimulationTime) { @@ -141,7 +142,6 @@ public class MotorClusterState { * Compute the average thrust over an interval. * * @param simulationTime - * @param cond * @return */ public double getThrust( final double simulationTime){ @@ -182,9 +182,9 @@ public class MotorClusterState { currentState = ThrustState.ARMED; } - public boolean testForIgnition( final FlightEvent _event ){ + public boolean testForIgnition(FlightConfiguration flightConfiguration, final FlightEvent _event ){ RocketComponent mount = (RocketComponent) this.getMount(); - return getIgnitionEvent().isActivationEvent( _event, mount); + return getIgnitionEvent().isActivationEvent(flightConfiguration, _event, mount); } public String toDescription(){ diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 9f285a709..828a49447 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -267,22 +267,25 @@ public class BarrowmanCalculatorTest { public void testContinuousRocket() { Rocket rocket = TestRockets.makeEstesAlphaIII(); AerodynamicCalculator calc = new BarrowmanCalculator(); + FlightConfiguration configuration = rocket.getSelectedConfiguration(); - assertTrue("Estes Alpha III should be continous: ", calc.isContinuous( rocket)); + assertTrue("Estes Alpha III should be continous: ", calc.isContinuous(configuration, rocket)); } @Test public void testContinuousRocketWithStrapOns() { Rocket rocket = TestRockets.makeFalcon9Heavy(); AerodynamicCalculator calc = new BarrowmanCalculator(); + FlightConfiguration configuration = rocket.getSelectedConfiguration(); - assertTrue("F9H should be continuous: ", calc.isContinuous( rocket)); + assertTrue("F9H should be continuous: ", calc.isContinuous(configuration, rocket)); } @Test public void testRadialDiscontinuousRocket() { Rocket rocket = TestRockets.makeEstesAlphaIII(); AerodynamicCalculator calc = new BarrowmanCalculator(); + FlightConfiguration configuration = rocket.getSelectedConfiguration(); NoseCone nose = (NoseCone)rocket.getChild(0).getChild(0); BodyTube body = (BodyTube)rocket.getChild(0).getChild(1); @@ -291,13 +294,14 @@ public class BarrowmanCalculatorTest { body.setOuterRadius( 0.012 ); body.setName( body.getName()+" << discontinuous"); - assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous( rocket)); + assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous(configuration, rocket)); } @Test public void testRadialDiscontinuityWithStrapOns() { Rocket rocket = TestRockets.makeFalcon9Heavy(); AerodynamicCalculator calc = new BarrowmanCalculator(); + FlightConfiguration configuration = rocket.getSelectedConfiguration(); final AxialStage coreStage = (AxialStage)rocket.getChild(1); final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0); @@ -309,7 +313,7 @@ public class BarrowmanCalculatorTest { body.setOuterRadius( 0.012 ); body.setName( body.getName()+" << discontinuous"); - assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous( rocket)); + assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous(configuration, rocket)); } @Test diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index c4ad802af..51db91e7d 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -294,9 +294,22 @@ public class FlightConfigurationTest extends BaseTestCase { config.toggleStage(0); assertThat(" toggle stage #0: ", config.isStageActive(0), equalTo(false)); - - AxialStage sustainer = rkt.getTopmostStage(); - AxialStage booster = rkt.getBottomCoreStage(); + + AxialStage sustainer = rkt.getTopmostStage(config); + AxialStage booster = rkt.getBottomCoreStage(config); + assertThat(" sustainer stage is stage #1: ", sustainer.getStageNumber(), equalTo(1)); + assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1)); + + config.setAllStages(); + config._setStageActive(1, false); + sustainer = rkt.getTopmostStage(config); + booster = rkt.getBottomCoreStage(config); + assertThat(" sustainer stage is stage #1: ", sustainer.getStageNumber(), equalTo(0)); + assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(0)); + + config.setAllStages(); + sustainer = rkt.getTopmostStage(config); + booster = rkt.getBottomCoreStage(config); assertThat(" sustainer stage is stage #0: ", sustainer.getStageNumber(), equalTo(0)); assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1)); @@ -351,7 +364,6 @@ public class FlightConfigurationTest extends BaseTestCase { selected.clearAllStages(); selected.toggleStage(1); - selected.toggleStage(2); // vvvv Test Target vvvv InstanceMap instances = selected.getActiveInstances(); diff --git a/core/test/net/sf/openrocket/simulation/DisableStageTest.java b/core/test/net/sf/openrocket/simulation/DisableStageTest.java new file mode 100644 index 000000000..9b4b0f7df --- /dev/null +++ b/core/test/net/sf/openrocket/simulation/DisableStageTest.java @@ -0,0 +1,305 @@ +package net.sf.openrocket.simulation; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +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; +import org.junit.Test; + +/** + * Test class that tests the effect on the simulation results of activating/deactivating stages. + * + * @author Sibo Van Gool + */ +public class DisableStageTest extends BaseTestCase { + /** + * Tests that the simulation results are correct when a single stage is deactivated and re-activated. + */ + @Test + public void testSingleStage() throws SimulationException { + //// Test disabling the stage + Rocket rocket = TestRockets.makeEstesAlphaIII(); + + Simulation simDisabled = new Simulation(rocket); + simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_0); + simDisabled.getActiveConfiguration()._setStageActive(0, false); + 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); + } catch (SimulationException e) { + if (!(e instanceof MotorIgnitionException)) { + Assert.fail("Simulation should have thrown a MotorIgnitionException"); + } + } + + //// Test re-enableing the stage. + Rocket rocketOriginal = TestRockets.makeEstesAlphaIII(); + + Simulation simOriginal = new Simulation(rocketOriginal); + simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_0); + simOriginal.getOptions().setISAAtmosphere(true); + simOriginal.getOptions().setTimeStep(0.05); + + simDisabled.getActiveConfiguration().setAllStages(); // Re-enable all stages. + + double delta = 0.05; // 5 % error margin (simulations are not exact) + compareSims(simOriginal, simDisabled, simulationListener, delta); + } + + /** + * Tests that the simulation results are correct when the last stage of a multi-stage rocket is deactivated and re-activated. + */ + @Test + public void testMultiStageLastDisabled() { + //// Test disabling the stage + Rocket rocketRemoved = TestRockets.makeBeta(); // Rocket with the last stage removed + Rocket rocketDisabled = TestRockets.makeBeta(); // Rocket with the last stage disabled + + int stageNr = rocketRemoved.getChildCount() - 1; + rocketRemoved.removeChild(stageNr); + FlightConfiguration fc = rocketDisabled.getFlightConfiguration(TestRockets.TEST_FCID_1); + fc._setStageActive(stageNr, false); + + Simulation simRemoved = new Simulation(rocketRemoved); + simRemoved.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simRemoved.getOptions().setISAAtmosphere(true); + simRemoved.getOptions().setTimeStep(0.05); + + Simulation simDisabled = new Simulation(rocketDisabled); + simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simDisabled.getOptions().setISAAtmosphere(true); + simDisabled.getOptions().setTimeStep(0.05); + + SimulationListener simulationListener = new AbstractSimulationListener(); + + double delta = 0.05; // 5 % error margin (simulations are not exact) + compareSims(simRemoved, simDisabled, simulationListener, delta); + + //// Test re-enableing the stage. + Rocket rocketOriginal = TestRockets.makeBeta(); + Simulation simOriginal = new Simulation(rocketOriginal); + simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simOriginal.getOptions().setISAAtmosphere(true); + simOriginal.getOptions().setTimeStep(0.05); + + simDisabled.getActiveConfiguration().setAllStages(); + + compareSims(simOriginal, simDisabled, simulationListener, delta); + } + + /** + * Tests that the simulation results are correct when the first stage of a multi-stage rocket is deactivated and re-activated. + */ + // Don't even know if this test was useful, but simulation results vary wildly because the first stage is disabled, + // so I'm just gonna ignore this test. + /*@Test + public void testMultiStageFirstDisabled() { + //// Test disabling the stage + Rocket rocketRemoved = TestRockets.makeBeta(); // Rocket with the last stage removed + Rocket rocketDisabled = TestRockets.makeBeta(); // Rocket with the last stage disabled + + // You need to disable the second stage body tube going into automatic radius mode, otherwise the + // removed and disabled rocket will have different results (removed rocket will have a different diameter) + BodyTube bodyTube = (BodyTube) rocketRemoved.getChild(1).getChild(0); + bodyTube.setOuterRadiusAutomatic(false); + + + int stageNr = 0; + rocketRemoved.removeChild(stageNr); + FlightConfiguration fc = rocketDisabled.getFlightConfiguration(TestRockets.TEST_FCID_1); + fc._setStageActive(stageNr, false); + + Simulation simRemoved = new Simulation(rocketRemoved); + simRemoved.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simRemoved.getOptions().setISAAtmosphere(true); + simRemoved.getOptions().setTimeStep(0.05); + + Simulation simDisabled = new Simulation(rocketDisabled); + simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simDisabled.getOptions().setISAAtmosphere(true); + simDisabled.getOptions().setTimeStep(0.05); + + SimulationListener simulationListener = new AbstractSimulationListener(); + + double delta = 0.1; // 10 % error margin (simulations are very unstable and not exact when the first stage is disabled...) + compareSims(simRemoved, simDisabled, simulationListener, delta); + + //// Test re-enableing the stage. + Rocket rocketOriginal = TestRockets.makeBeta(); + Simulation simOriginal = new Simulation(rocketOriginal); + simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_1); + simOriginal.getOptions().setISAAtmosphere(true); + simOriginal.getOptions().setTimeStep(0.05); + + simDisabled.getActiveConfiguration().setAllStages(); + + compareSims(simOriginal, simDisabled, simulationListener, delta); + }*/ + + /** + * Tests that the simulation results are correct when a booster stage is deactivated and re-activated. + */ + @Test + public void testBooster1() { + //// Test disabling the stage + Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed + Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled + + FlightConfigurationId fcid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1); + int stageNr = 2; // Stage 2 is the Parallel Booster Stage + rocketRemoved.getChild(1).getChild(0).removeChild(0); // Remove the Parallel Booster Stage + FlightConfiguration fc = rocketDisabled.getFlightConfiguration(fcid); + fc._setStageActive(stageNr, false); + + Simulation simRemoved = new Simulation(rocketRemoved); + simRemoved.setFlightConfigurationId(fcid); + simRemoved.getOptions().setISAAtmosphere(true); + simRemoved.getOptions().setTimeStep(0.05); + + Simulation simDisabled = new Simulation(rocketDisabled); + simDisabled.setFlightConfigurationId(fcid); + simDisabled.getOptions().setISAAtmosphere(true); + simDisabled.getOptions().setTimeStep(0.05); + + SimulationListener simulationListener = new AbstractSimulationListener(); + + double delta = 0.05; // 5 % error margin (simulations are not exact) + compareSims(simRemoved, simDisabled, simulationListener, delta); + + //// Test re-enableing the stage. + Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); + Simulation simOriginal = new Simulation(rocketOriginal); + simOriginal.setFlightConfigurationId(fcid); + simOriginal.getOptions().setISAAtmosphere(true); + simOriginal.getOptions().setTimeStep(0.05); + + simDisabled.getActiveConfiguration().setAllStages(); + + compareSims(simOriginal, simDisabled, simulationListener, delta); + } + + /** + * Tests that the simulation results are correct when the parent stage of a booster stage is deactivated and re-activated. + */ + @Test + public void testBooster2() { + //// Test disabling the stage + Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed + Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled + + FlightConfigurationId fid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1); + int stageNr = 1; // Stage 1 is the Parallel Booster Stage's parent stage + rocketRemoved.getChild(1).removeChild(0); // Remove the Parallel Booster Stage's parent stage + FlightConfiguration fc = rocketDisabled.getFlightConfiguration(fid); + fc._setStageActive(stageNr, false); + + Simulation simRemoved = new Simulation(rocketRemoved); + simRemoved.setFlightConfigurationId(fid); + simRemoved.getOptions().setISAAtmosphere(true); + simRemoved.getOptions().setTimeStep(0.05); + + Simulation simDisabled = new Simulation(rocketDisabled); + simDisabled.setFlightConfigurationId(fid); + 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); + } catch (SimulationException e) { + if (!(e instanceof MotorIgnitionException)) { + Assert.fail("Simulation failed: " + e); + } + } + + try { + simDisabled.simulate(simulationListener); + } catch (SimulationException e) { + if (!(e instanceof MotorIgnitionException)) { + Assert.fail("Simulation failed: " + e); + } + } + + //// Test re-enableing the stage. + Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); + Simulation simOriginal = new Simulation(rocketOriginal); + simOriginal.setFlightConfigurationId(fid); + simOriginal.getOptions().setISAAtmosphere(true); + simOriginal.getOptions().setTimeStep(0.05); + + simDisabled.getActiveConfiguration().setAllStages(); + + double delta = 0.05; // 5 % error margin (simulations are not exact) + compareSims(simOriginal, simDisabled, simulationListener, delta); + } + + /** + * Compare simActual to simExpected and fail the unit test if there was an error during simulation or + * the two don't match. + * Tested parameters: + * - maxAcceleration + * - maxAltitude + * - maxVelocity + * - maxMachNumber + * - flightTime + * - launchRodVelocity + * - deploymentVelocity + * - 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) { + try { + simExpected.simulate(simulationListener); + double maxAccelerationOriginal = simExpected.getSimulatedData().getMaxAcceleration(); + double maxAltitudeOriginal = simExpected.getSimulatedData().getMaxAltitude(); + double maxVelocityOriginal = simExpected.getSimulatedData().getMaxVelocity(); + double maxMachNumberOriginal = simExpected.getSimulatedData().getMaxMachNumber(); + double flightTimeOriginal = simExpected.getSimulatedData().getFlightTime(); + double timeToApogeeOriginal = simExpected.getSimulatedData().getTimeToApogee(); + double launchRodVelocityOriginal = simExpected.getSimulatedData().getLaunchRodVelocity(); + double deploymentVelocityOriginal = simExpected.getSimulatedData().getDeploymentVelocity(); + double groundHitVelocityOriginal = simExpected.getSimulatedData().getGroundHitVelocity(); + + simActual.simulate(simulationListener); + double maxAccelerationDisabled = simActual.getSimulatedData().getMaxAcceleration(); + double maxAltitudeDisabled = simActual.getSimulatedData().getMaxAltitude(); + double maxVelocityDisabled = simActual.getSimulatedData().getMaxVelocity(); + double maxMachNumberDisabled = simActual.getSimulatedData().getMaxMachNumber(); + double flightTimeDisabled = simActual.getSimulatedData().getFlightTime(); + double timeToApogeeDisabled = simActual.getSimulatedData().getTimeToApogee(); + double launchRodVelocityDisabled = simActual.getSimulatedData().getLaunchRodVelocity(); + double deploymentVelocityDisabled = simActual.getSimulatedData().getDeploymentVelocity(); + double groundHitVelocityDisabled = simActual.getSimulatedData().getGroundHitVelocity(); + + Assert.assertEquals(maxAccelerationOriginal, maxAccelerationDisabled, maxAccelerationOriginal * delta); + Assert.assertEquals(maxAltitudeOriginal, maxAltitudeDisabled, maxAltitudeOriginal * delta); + Assert.assertEquals(maxVelocityOriginal, maxVelocityDisabled, maxVelocityOriginal * delta); + Assert.assertEquals(maxMachNumberOriginal, maxMachNumberDisabled, maxMachNumberOriginal * delta); + Assert.assertEquals(flightTimeOriginal, flightTimeDisabled, flightTimeOriginal * delta); + Assert.assertEquals(timeToApogeeOriginal, timeToApogeeDisabled, timeToApogeeOriginal * delta); + Assert.assertEquals(launchRodVelocityOriginal, launchRodVelocityDisabled, launchRodVelocityOriginal * delta); + Assert.assertEquals(deploymentVelocityOriginal, deploymentVelocityDisabled, deploymentVelocityOriginal * delta); + Assert.assertEquals(groundHitVelocityOriginal, groundHitVelocityDisabled, groundHitVelocityOriginal * delta); + } catch (SimulationException e) { + Assert.fail("Simulation failed: " + e); + } + } +}