diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/DocumentConfig.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/DocumentConfig.java
index 3b31a2a58..488fdd7c8 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/DocumentConfig.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/DocumentConfig.java
@@ -495,6 +495,9 @@ class DocumentConfig {
Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationEvent",
StageSeparationConfiguration.SeparationEvent.class),
StageSeparationConfiguration.SeparationEvent.class));
+ setters.put("AxialStage:separationaltitude", new DoubleSetter(
+ Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
+ Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationAltitude", double.class)));
setters.put("AxialStage:separationdelay", new DoubleSetter(
Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationDelay", double.class)));
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/StageSeparationConfigurationHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/StageSeparationConfigurationHandler.java
index 7031a9e3b..98ba11da7 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/StageSeparationConfigurationHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/StageSeparationConfigurationHandler.java
@@ -19,6 +19,7 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
private final AxialStage stage;
private SeparationEvent event = null;
+ private double altitude = Double.NaN;
private double delay = Double.NaN;
public StageSeparationConfigurationHandler(AxialStage stage, DocumentLoadingContext context) {
@@ -30,6 +31,9 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
if (event != null) {
config.setSeparationEvent(event);
}
+ if (!Double.isNaN(altitude)) {
+ config.setSeparationAltitude(altitude);
+ }
if (!Double.isNaN(delay)) {
config.setSeparationDelay(delay);
}
@@ -55,6 +59,9 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
return;
}
return;
+ } else if ("separationaltitude".equals(element)) {
+ altitude = parseDouble(content, warnings, Warning.FILE_INVALID_PARAMETER);
+ return;
} else if ("separationdelay".equals(element)) {
delay = parseDouble(content, warnings, Warning.FILE_INVALID_PARAMETER);
return;
@@ -73,4 +80,4 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
stage.getSeparationConfigurations().set(fcid, getConfiguration(sepConfig));
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/savers/AxialStageSaver.java b/core/src/main/java/info/openrocket/core/file/openrocket/savers/AxialStageSaver.java
index 4cca96ac7..013f01fa2 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/savers/AxialStageSaver.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/savers/AxialStageSaver.java
@@ -71,8 +71,9 @@ public class AxialStageSaver extends ComponentAssemblySaver {
elements.add((indent ? " " : "") + ""
+ config.getSeparationEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "")
+ "");
+ elements.add((indent ? " " : "") + "" + config.getSeparationAltitude() + "");
elements.add((indent ? " " : "") + "" + config.getSeparationDelay() + "");
return elements;
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/StageSeparationConfiguration.java b/core/src/main/java/info/openrocket/core/rocketcomponent/StageSeparationConfiguration.java
index 6acf34d93..db7a0ee2a 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/StageSeparationConfiguration.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/StageSeparationConfiguration.java
@@ -4,6 +4,7 @@ import info.openrocket.core.l10n.Translator;
import info.openrocket.core.simulation.FlightEvent;
import info.openrocket.core.startup.Application;
import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.Pair;
import java.util.LinkedList;
import java.util.List;
@@ -12,22 +13,17 @@ import java.util.Objects;
public class StageSeparationConfiguration implements FlightConfigurableParameter {
public static enum SeparationEvent {
- //// Upper stage motor ignition
- UPPER_IGNITION(trans.get("Stage.SeparationEvent.UPPER_IGNITION")) {
+ //// Launch
+ LAUNCH(trans.get("Stage.SeparationEvent.LAUNCH")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
- if (e.getType() != FlightEvent.Type.IGNITION)
- return false;
-
- int ignition = e.getSource().getStageNumber();
- int mount = stage.getStageNumber();
- return (mount == ignition + 1);
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
+ return e.getType() == FlightEvent.Type.LAUNCH;
}
},
//// Current stage motor ignition
IGNITION(trans.get("Stage.SeparationEvent.IGNITION")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.IGNITION)
return false;
@@ -39,7 +35,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
//// Current stage motor burnout
BURNOUT(trans.get("Stage.SeparationEvent.BURNOUT")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.BURNOUT)
return false;
@@ -51,7 +47,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
//// Current stage ejection charge
EJECTION(trans.get("Stage.SeparationEvent.EJECTION")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.EJECTION_CHARGE)
return false;
@@ -60,17 +56,58 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
return (mount == ignition);
}
},
- //// Launch
- LAUNCH(trans.get("Stage.SeparationEvent.LAUNCH")) {
+ //// Upper stage motor ignition
+ UPPER_IGNITION(trans.get("Stage.SeparationEvent.UPPER_IGNITION")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
- return e.getType() == FlightEvent.Type.LAUNCH;
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
+ if (e.getType() != FlightEvent.Type.IGNITION)
+ return false;
+
+ int ignition = e.getSource().getStageNumber();
+ int mount = stage.getStageNumber();
+ return (mount == ignition + 1);
}
},
+ //// Fixed altitude (ascending)
+ ALTITUDE_ASCENDING(trans.get("Stage.SeparationEvent.ALTITUDE_ASCENDING")) {
+ @Override
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
+ if ((e.getType() != FlightEvent.Type.ALTITUDE) || (e.getData() == null))
+ return false;
+
+ double alt = config.getSeparationAltitude();
+ Pair altitude = (Pair) e.getData();
+
+ return (altitude.getU() <= alt) && (altitude.getV() >= alt);
+ }
+ },
+
+ //// Apogee
+ APOGEE(trans.get("Stage.SeparationEvent.APOGEE")) {
+ @Override
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
+ return e.getType() == FlightEvent.Type.APOGEE;
+ }
+ },
+
+ //// Fixed altitude (descending)
+ ALTITUDE_DESCENDING(trans.get("Stage.SeparationEvent.ALTITUDE_DESCENDING")) {
+ @Override
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
+ if ((e.getType() != FlightEvent.Type.ALTITUDE) || (e.getData() == null))
+ return false;
+
+ double alt = config.getSeparationAltitude();
+ Pair altitude = (Pair) e.getData();
+
+ return (altitude.getU() >= alt) && (altitude.getV() <= alt);
+ }
+ },
+
//// Never
NEVER(trans.get("Stage.SeparationEvent.NEVER")) {
@Override
- public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
+ public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
return false;
}
},
@@ -85,8 +122,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
/**
* Test whether a specific event is a stage separation event.
*/
- public abstract boolean isSeparationEvent(FlightEvent e, AxialStage stage);
-
+ public abstract boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage);
@Override
public String toString() {
return description;
@@ -96,6 +132,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
private static final Translator trans = Application.getTranslator();
private SeparationEvent separationEvent = SeparationEvent.EJECTION;
+ private double separationAltitude = 200;
private double separationDelay = 0;
private final List configListeners = new LinkedList<>();
@@ -119,6 +156,21 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
fireChangeEvent();
}
+ public double getSeparationAltitude() {
+ return separationAltitude;
+ }
+
+ public void setSeparationAltitude(double separationAltitude) {
+ for (StageSeparationConfiguration listener : configListeners) {
+ listener.setSeparationAltitude(separationAltitude);
+ }
+
+ if (MathUtil.equals(this.separationAltitude, separationAltitude)) {
+ return;
+ }
+ this.separationAltitude = separationAltitude;
+ }
+
public double getSeparationDelay() {
return separationDelay;
}
@@ -152,6 +204,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
public StageSeparationConfiguration copy(final FlightConfigurationId copyId) {
StageSeparationConfiguration clone = new StageSeparationConfiguration();
clone.separationEvent = this.separationEvent;
+ clone.separationAltitude = this.separationAltitude;
clone.separationDelay = this.separationDelay;
return clone;
}
diff --git a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
index aeae329b3..2b61f0a65 100644
--- a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
+++ b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
@@ -362,9 +362,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
int stageNo = stage.getStageNumber();
if (stageNo == 0)
continue;
-
+
StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(this.fcid);
- if (separationConfig.getSeparationEvent().isSeparationEvent(event, stage)) {
+ if (separationConfig.getSeparationEvent().isSeparationEvent(separationConfig, event, stage)) {
currentStatus.addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION,
event.getTime() + separationConfig.getSeparationDelay(), stage));
}
diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties
index 679525eac..b2b2001a3 100644
--- a/core/src/main/resources/l10n/messages.properties
+++ b/core/src/main/resources/l10n/messages.properties
@@ -1192,6 +1192,7 @@ ComponentCfgDlg.ModifyComponents = Modify components
ComponentAssemblyConfig.tab.Separation = Separation
ComponentAssemblyConfig.tab.Separation.ttip = Stage separation options
ComponentAssemblyConfig.separation.lbl.title = Select when this stage separates:
+ComponentAssemblyConfig.separation.lbl.SeparatesAt = Upper stage separates at:
ComponentAssemblyConfig.separation.lbl.plus = plus
ComponentAssemblyConfig.separation.lbl.seconds = seconds
ComponentAssemblyConfig.parallel.radius = Radial Distance:
@@ -1892,11 +1893,14 @@ Transition.Transition = Transition
!Stage
Stage.Stage = Stage
-Stage.SeparationEvent.UPPER_IGNITION = Upper stage motor ignition
+Stage.SeparationEvent.LAUNCH = Launch
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.UPPER_IGNITION = Upper stage motor ignition
+Stage.SeparationEvent.ALTITUDE_ASCENDING = Specific altitude during ascent
+Stage.SeparationEvent.APOGEE = Apogee
+Stage.SeparationEvent.ALTITUDE_DESCENDING = Specific altitude during descent
Stage.SeparationEvent.NEVER = Never
BoosterSet.BoosterSet = Booster Set
diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
index 6d357fad9..ed1c3e6e2 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
@@ -13,6 +13,7 @@ import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
+import javax.swing.JSlider;
import javax.swing.JSpinner;
import info.openrocket.core.formatting.RocketDescriptor;
@@ -29,6 +30,8 @@ import net.miginfocom.swing.MigLayout;
import info.openrocket.swing.gui.SpinnerEditor;
import info.openrocket.swing.gui.adaptors.DoubleModel;
import info.openrocket.swing.gui.adaptors.EnumModel;
+import info.openrocket.swing.gui.components.BasicSlider;
+import info.openrocket.swing.gui.components.UnitSelector;
import info.openrocket.swing.gui.util.GUIUtil;
import info.openrocket.swing.gui.widgets.SelectColorButton;
@@ -40,6 +43,11 @@ public class SeparationSelectionDialog extends JDialog {
private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
private StageSeparationConfiguration newConfiguration;
+
+ private final JLabel altText;
+ private final JSpinner altSpinner;
+ private final UnitSelector altUnit;
+ private final JSlider altSlider;
private boolean isOverrideDefault;
@@ -53,7 +61,6 @@ public class SeparationSelectionDialog extends JDialog {
JPanel panel = new JPanel(new MigLayout("fill"));
-
// Select separation event
panel.add(new JLabel(trans.get("SeparationSelectionDialog.opt.title")), "span, wrap rel");
@@ -77,10 +84,28 @@ public class SeparationSelectionDialog extends JDialog {
if (isOverridden) {
overrideButton.setSelected(true);
}
-
+
+ //// Separation
+ //// Stages separate at:
+ panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.SeparatesAt")), "");
+
final JComboBox event = new JComboBox(new EnumModel(newConfiguration, "SeparationEvent"));
event.setSelectedItem(newConfiguration.getSeparationEvent());
- panel.add(event, "wrap rel");
+ panel.add(event, "spanx 3, growx, wrap");
+
+ // Altitude:
+ altText = new JLabel(trans.get("ParachuteCfg.lbl.Altitude"));
+ panel.add(altText);
+
+ final DoubleModel alt = new DoubleModel(newConfiguration, "SeparationAltitude", UnitGroup.UNITS_DISTANCE, 0);
+
+ altSpinner = new JSpinner(alt.getSpinnerModel());
+ altSpinner.setEditor(new SpinnerEditor(altSpinner));
+ panel.add(altSpinner, "growx");
+ altUnit = new UnitSelector(alt);
+ panel.add(altUnit, "growx");
+ altSlider = new BasicSlider(alt.getSliderModel(100, 1000));
+ panel.add(altSlider, "w 100lp, wrap");
// ... and delay
panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.plus")), "alignx 100%");
@@ -92,8 +117,15 @@ public class SeparationSelectionDialog extends JDialog {
//// seconds
panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.seconds")), "wrap para");
-
-
+
+ event.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateState();
+ }
+ });
+ updateState();
+
panel.add(new JPanel(), "span, split, growx");
JButton okButton = new SelectColorButton(trans.get("button.ok"));
@@ -131,6 +163,17 @@ public class SeparationSelectionDialog extends JDialog {
GUIUtil.installEscapeCloseButtonOperation(this, okButton);
}
+
+ private void updateState() {
+ boolean enabled = ((newConfiguration.getSeparationEvent() == SeparationEvent.ALTITUDE_ASCENDING) ||
+ (newConfiguration.getSeparationEvent() == SeparationEvent.ALTITUDE_DESCENDING));
+
+ altText.setEnabled(enabled);
+ altSpinner.setEnabled(enabled);
+ altUnit.setEnabled(enabled);
+ altSlider.setEnabled(enabled);
+ }
+
/**
* Returns true if this dialog was used to override the default configuration.
* @return true if this dialog was used to override the default configuration.