+2012-02-10 Sampo Niskanen
+
+ * Configurable stage separation events
+
+2012-00-00 Doug Pedrick
+
+ * RKT saving support
+
2011-11-24 Sampo Niskanen
* Released version 1.1.9
ComponentCfgDlg.configuration1 =
ComponentCfgDlg.Modify = Modify
+!StageConfig
+StageConfig.tab.Separation = Separation
+StageConfig.tab.Separation.ttip = Stage separation options
+StageConfig.separation.lbl.title = Select when this stage separates:
+StageConfig.separation.lbl.plus = plus
+StageConfig.separation.lbl.seconds = seconds
+
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Number of fins:
EllipticalFinSetCfg.Rotation = Rotation:
Transition.Transition = Transition
!Stage
Stage.Stage = Stage
+
+Stage.SeparationEvent.UPPER_IGNITION = Upper stage motor ignition
+Stage.SeparationEvent.IGNITION = Current stage motor ignition
+Stage.SeparationEvent.BURNOUT = Current stage motor burnout
+Stage.SeparationEvent.EJECTION = Current stage ejection charge
+Stage.SeparationEvent.LAUNCH = Launch
+Stage.SeparationEvent.NEVER = Never
+
! BodyTube
BodyTube.BodyTube = Body tube
! TubeCoupler
Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
setters.put("Rocket:revision", new StringSetter(
Reflection.findMethod(Rocket.class, "setRevision", String.class)));
+
+ // Stage
+ setters.put("Stage:separationevent", new EnumSetter<Stage.SeparationEvent>(
+ Reflection.findMethod(Stage.class, "setSeparationEvent", Stage.SeparationEvent.class),
+ Stage.SeparationEvent.class));
+ setters.put("Stage:separationdelay", new DoubleSetter(
+ Reflection.findMethod(Stage.class, "setSeparationDelay", double.class)));
+
}
package net.sf.openrocket.file.openrocket.savers;
import java.util.ArrayList;
+import java.util.List;
-public class StageSaver extends ComponentAssemblySaver {
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
+public class StageSaver extends ComponentAssemblySaver {
+
private static final StageSaver instance = new StageSaver();
public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
ArrayList<String> list = new ArrayList<String>();
list.add("<stage>");
- instance.addParams(c,list);
+ instance.addParams(c, list);
list.add("</stage>");
return list;
}
-
+ @Override
+ protected void addParams(RocketComponent c, List<String> elements) {
+ super.addParams(c, elements);
+ Stage stage = (Stage) c;
+
+ if (stage.getStageNumber() > 0) {
+ elements.add("<separationevent>"
+ + stage.getSeparationEvent().name().toLowerCase().replace("_", "")
+ + "</separationevent>");
+ elements.add("<separationdelay>" + stage.getSeparationDelay() + "</separationdelay>");
+ }
+ }
}
--- /dev/null
+package net.sf.openrocket.gui.configdialog;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.adaptors.EnumModel;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.rocketcomponent.Stage.SeparationEvent;
+import net.sf.openrocket.startup.Application;
+
+public class StageConfig extends RocketComponentConfig {
+ private static final Translator trans = Application.getTranslator();
+
+ public StageConfig(OpenRocketDocument document, RocketComponent component) {
+ super(document, component);
+
+ // Stage separation config (for non-first stage)
+ if (component.getStageNumber() > 0) {
+ JPanel tab = separationTab((Stage) component);
+ tabbedPane.insertTab(trans.get("tab.Separation"), null, tab,
+ trans.get("tab.Separation.ttip"), 1);
+ }
+
+ }
+
+
+ private JPanel separationTab(Stage stage) {
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ // Select separation event
+ panel.add(new StyledLabel(trans.get("separation.lbl.title"), Style.BOLD), "spanx, wrap rel");
+
+ JComboBox combo = new JComboBox(new EnumModel<SeparationEvent>(stage, "SeparationEvent"));
+ panel.add(combo, "");
+
+ // ... and delay
+ panel.add(new JLabel(trans.get("separation.lbl.plus")), "");
+
+ DoubleModel dm = new DoubleModel(stage, "SeparationDelay", 0);
+ JSpinner spin = new JSpinner(dm.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin, "");
+
+ //// seconds
+ panel.add(new JLabel(trans.get("separation.lbl.seconds")), "wrap unrel");
+
+ return panel;
+ }
+
+}
package net.sf.openrocket.rocketcomponent;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.MathUtil;
public class Stage extends ComponentAssembly {
-
+
private static final Translator trans = Application.getTranslator();
-
- @Override
- public String getComponentName () {
- //// Stage
- return trans.get("Stage.Stage");
- }
-
-
+
+
+ public static enum SeparationEvent {
+ //// Upper stage motor ignition
+ UPPER_IGNITION("Stage.SeparationEvent.UPPER_IGNITION") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ if (e.getType() != FlightEvent.Type.IGNITION)
+ return false;
+
+ int ignition = e.getSource().getStageNumber();
+ int mount = stage.getStageNumber();
+ return (mount == ignition + 1);
+ }
+ },
+ //// Current stage motor ignition
+ IGNITION("Stage.SeparationEvent.IGNITION") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ if (e.getType() != FlightEvent.Type.IGNITION)
+ return false;
+
+ int ignition = e.getSource().getStageNumber();
+ int mount = stage.getStageNumber();
+ return (mount == ignition);
+ }
+ },
+ //// Current stage motor burnout
+ BURNOUT("Stage.SeparationEvent.BURNOUT") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ if (e.getType() != FlightEvent.Type.BURNOUT)
+ return false;
+
+ int ignition = e.getSource().getStageNumber();
+ int mount = stage.getStageNumber();
+ return (mount == ignition);
+ }
+ },
+ //// Current stage ejection charge
+ EJECTION("Stage.SeparationEvent.EJECTION") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ if (e.getType() != FlightEvent.Type.EJECTION_CHARGE)
+ return false;
+
+ int ignition = e.getSource().getStageNumber();
+ int mount = stage.getStageNumber();
+ return (mount == ignition);
+ }
+ },
+ //// Launch
+ LAUNCH("Stage.SeparationEvent.LAUNCH") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ return e.getType() == FlightEvent.Type.LAUNCH;
+ }
+ },
+ //// Never
+ NEVER("Stage.SeparationEvent.NEVER") {
+ @Override
+ public boolean isSeparationEvent(FlightEvent e, Stage stage) {
+ return false;
+ }
+ },
+ ;
+
+
+ private final String description;
+
+ SeparationEvent(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Test whether a specific event is a stage separation event.
+ */
+ public abstract boolean isSeparationEvent(FlightEvent e, Stage stage);
+
+ @Override
+ public String toString() {
+ return trans.get(description);
+ }
+ };
+
+
+ private SeparationEvent separationEvent = SeparationEvent.UPPER_IGNITION;
+ private double separationDelay = 0;
+
+
+ @Override
+ public String getComponentName() {
+ //// Stage
+ return trans.get("Stage.Stage");
+ }
+
+
+ public SeparationEvent getSeparationEvent() {
+ return separationEvent;
+ }
+
+
+ public void setSeparationEvent(SeparationEvent separationEvent) {
+ if (separationEvent == this.separationEvent)
+ return;
+ this.separationEvent = separationEvent;
+ fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
+ }
+
+
+ public double getSeparationDelay() {
+ return separationDelay;
+ }
+
+
+ public void setSeparationDelay(double separationDelay) {
+ if (MathUtil.equals(separationDelay, this.separationDelay))
+ return;
+ this.separationDelay = separationDelay;
+ fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
+ }
+
+
+
@Override
public boolean allowsChildren() {
return true;
}
-
- /**
+
+ /**
* Check whether the given type can be added to this component. A Stage allows
* only BodyComponents to be added.
- *
- * @param type The RocketComponent class type to add.
- *
- * @return Whether such a component can be added.
- */
- @Override
- public boolean isCompatible (Class<? extends RocketComponent> type) {
- return BodyComponent.class.isAssignableFrom(type);
- }
+ *
+ * @param type The RocketComponent class type to add.
+ *
+ * @return Whether such a component can be added.
+ */
+ @Override
+ public boolean isCompatible(Class<? extends RocketComponent> type) {
+ return BodyComponent.class.isAssignableFrom(type);
+ }
}
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationLaunchException;
status = initialStatus(configuration, motorConfiguration, simulationConditions, flightData);
status = currentStepper.initialize(status);
-
+
SimulationListenerHelper.fireStartSimulation(status);
// Get originating position (in case listener has modified launch position)
Coordinate origin = status.getRocketPosition();
}
SimulationListenerHelper.firePostStep(status);
-
+
// Check for NaN values in the simulation status
checkNaN();
maxAlt = status.getRocketPosition().z;
}
-
+
// Position relative to start location
Coordinate relativePosition = status.getRocketPosition().sub(origin);
addEvent(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.getSimulationTime(), null));
}
-
+
// Check for apogee
if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) {
addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(),
status.getConfiguration().getRocket()));
}
-
+
// Check for burnt out motors
for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) {
MotorInstance motor = status.getMotorConfiguration().getMotorInstance(motorId);
}
-
+
private SimulationStatus initialStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions, FlightData flightData) {
init.setRocketOrientationQuaternion(o);
init.setRocketRotationVelocity(Coordinate.NUL);
-
+
/*
* Calculate the effective launch rod length taking into account launch lugs.
* If no lugs are found, assume a tower launcher of full length.
}
init.setEffectiveLaunchRodLength(length);
-
-
+
+
init.setSimulationStartWallTime(System.nanoTime());
init.setMotorIgnited(false);
}
-
+
/**
* Create a rocket configuration from the launch conditions.
*
}
-
+
/**
* Create a new motor instance configuration for the rocket configuration.
*
for (event = nextEvent(); event != null; event = nextEvent()) {
+ // Ignore events for components that are no longer attached to the rocket
+ if (event.getSource() != null && event.getSource().getParent() != null &&
+ !status.getConfiguration().isStageActive(event.getSource().getStageNumber())) {
+ continue;
+ }
+
// Call simulation listeners, allow aborting event handling
if (!SimulationListenerHelper.fireHandleFlightEvent(status, event)) {
continue;
}
}
-
-
+
+
// Check for motor ignition events, add ignition events to queue
for (MotorId id : status.getMotorConfiguration().getMotorIDs()) {
MotorMount mount = status.getMotorConfiguration().getMotorMount(id);
}
}
-
+
+ // Check for stage separation event
+ for (int stageNo : status.getConfiguration().getActiveStages()) {
+ if (stageNo == 0)
+ continue;
+
+ Stage stage = (Stage) status.getConfiguration().getRocket().getChild(stageNo);
+ if (stage.getSeparationEvent().isSeparationEvent(event, stage)) {
+ addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION,
+ event.getTime() + stage.getSeparationDelay(), stage));
+ }
+ }
+
+
// Check for recovery device deployment, add events to queue
Iterator<RocketComponent> rci = status.getConfiguration().iterator();
while (rci.hasNext()) {
}
}
-
+
// Handle event
switch (event.getType()) {
status.getFlightData().addEvent(event);
break;
}
-
+
case IGNITION: {
// Ignite the motor
MotorMount mount = (MotorMount) event.getSource();
status.setMotorIgnited(true);
status.getFlightData().addEvent(event);
- // Add stage separation event if appropriate
- int n = component.getStageNumber();
- if (n < component.getRocket().getStageCount() - 1) {
- if (status.getConfiguration().isStageActive(n + 1)) {
- addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, event.getTime(),
- component.getStage()));
- }
- }
break;
}
-
+
case LIFTOFF: {
// Mark lift-off as occurred
status.setLiftoff(true);
status.getFlightData().addEvent(event);
break;
}
-
+
case LAUNCHROD: {
// Mark launch rod as cleared
status.setLaunchRodCleared(true);
status.getFlightData().addEvent(event);
break;
}
-
+
case BURNOUT: {
// If motor burnout occurs without lift-off, abort
if (!status.isLiftoff()) {
status.getFlightData().addEvent(event);
break;
}
-
+
case EJECTION_CHARGE: {
status.getFlightData().addEvent(event);
break;
}
-
+
case STAGE_SEPARATION: {
// TODO: HIGH: Store lower stages to be simulated later
RocketComponent stage = event.getSource();
int n = stage.getStageNumber();
- status.getConfiguration().setToStage(n);
+ status.getConfiguration().setToStage(n - 1);
status.getFlightData().addEvent(event);
break;
}
-
+
case APOGEE:
// Mark apogee as reached
status.setApogeeReached(true);
}
-
+
// If no motor has ignited, abort
if (!status.isMotorIgnited()) {
throw new MotorIgnitionException("No motors ignited.");
return ret;
}
-
/**
* Add a flight event to the event queue unless a listener aborts adding it.
*
}
-
+
/**
* Return the next flight event to handle, or null if no more events should be handled.
* This method jumps the simulation time forward in case no motors have been ignited.
}
-
+
private void checkNaN() throws SimulationException {
double d = 0;
boolean b = false;
}
}
-
+
}
*/
public class FlightEvent implements Comparable<FlightEvent> {
private static final Translator trans = Application.getTranslator();
-
+
/**
* The type of the flight event.
*
/**
* Rocket launch.
*/
- //// Launch
LAUNCH(trans.get("FlightEvent.Type.LAUNCH")),
/**
* Ignition of a motor. Source is the motor mount the motor of which has ignited,
* and the data is the MotorId of the motor instance.
*/
- //// Motor ignition
IGNITION(trans.get("FlightEvent.Type.IGNITION")),
/**
* When the motor has lifted off the ground.
*/
- //// Lift-off
LIFTOFF(trans.get("FlightEvent.Type.LIFTOFF")),
/**
* Launch rod has been cleared.
*/
- //// Launch rod clearance
LAUNCHROD(trans.get("FlightEvent.Type.LAUNCHROD")),
/**
* Burnout of a motor. Source is the motor mount the motor of which has burnt out,
* and the data is the MotorId of the motor instance.
*/
- //// Motor burnout
BURNOUT(trans.get("FlightEvent.Type.BURNOUT")),
/**
* Ejection charge of a motor fired. Source is the motor mount the motor of
* which has exploded its ejection charge, and data is the MotorId of the motor instance.
*/
- //// Ejection charge
EJECTION_CHARGE(trans.get("FlightEvent.Type.EJECTION_CHARGE")),
/**
- * Separation of a stage. Source is the stage which has separated all lower stages.
+ * Separation of a stage. Source is the stage which is being separated from the upper stages.
*/
- //// Stage separation
STAGE_SEPARATION(trans.get("FlightEvent.Type.STAGE_SEPARATION")),
/**
* Apogee has been reached.
*/
- //// Apogee
APOGEE(trans.get("FlightEvent.Type.APOGEE")),
/**
* Opening of a recovery device. Source is the RecoveryComponent which has opened.
*/
- //// Recovery device deployment
RECOVERY_DEVICE_DEPLOYMENT(trans.get("FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT")),
/**
* Ground has been hit after flight.
*/
- //// Ground hit
GROUND_HIT(trans.get("FlightEvent.Type.GROUND_HIT")),
-
+
/**
* End of simulation. Placing this to the queue will end the simulation.
*/
- //// Simulation end
SIMULATION_END(trans.get("FlightEvent.Type.SIMULATION_END")),
-
+
/**
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
* which contains the old and new altitudes.
*/
- //// Altitude change
ALTITUDE(trans.get("FlightEvent.Type.ALTITUDE"));
private final String name;
}
-
+
public Type getType() {
return type;
}