Create stage separation conditions of specific altitude during ascent, apogee, and specific altitude during descent to support payloads with separate descent

This commit is contained in:
JoePfeiffer 2024-08-08 20:35:08 -06:00
parent 194f1539ec
commit ef9a5d6c2b
7 changed files with 141 additions and 30 deletions

View File

@ -495,6 +495,9 @@ class DocumentConfig {
Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationEvent", Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationEvent",
StageSeparationConfiguration.SeparationEvent.class), StageSeparationConfiguration.SeparationEvent.class),
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( setters.put("AxialStage:separationdelay", new DoubleSetter(
Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"), Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationDelay", double.class))); Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationDelay", double.class)));

View File

@ -19,6 +19,7 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
private final AxialStage stage; private final AxialStage stage;
private SeparationEvent event = null; private SeparationEvent event = null;
private double altitude = Double.NaN;
private double delay = Double.NaN; private double delay = Double.NaN;
public StageSeparationConfigurationHandler(AxialStage stage, DocumentLoadingContext context) { public StageSeparationConfigurationHandler(AxialStage stage, DocumentLoadingContext context) {
@ -30,6 +31,9 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
if (event != null) { if (event != null) {
config.setSeparationEvent(event); config.setSeparationEvent(event);
} }
if (!Double.isNaN(altitude)) {
config.setSeparationAltitude(altitude);
}
if (!Double.isNaN(delay)) { if (!Double.isNaN(delay)) {
config.setSeparationDelay(delay); config.setSeparationDelay(delay);
} }
@ -55,6 +59,9 @@ class StageSeparationConfigurationHandler extends AbstractElementHandler {
return; return;
} }
return; return;
} else if ("separationaltitude".equals(element)) {
altitude = parseDouble(content, warnings, Warning.FILE_INVALID_PARAMETER);
return;
} else if ("separationdelay".equals(element)) { } else if ("separationdelay".equals(element)) {
delay = parseDouble(content, warnings, Warning.FILE_INVALID_PARAMETER); delay = parseDouble(content, warnings, Warning.FILE_INVALID_PARAMETER);
return; return;

View File

@ -71,6 +71,7 @@ public class AxialStageSaver extends ComponentAssemblySaver {
elements.add((indent ? " " : "") + "<separationevent>" elements.add((indent ? " " : "") + "<separationevent>"
+ config.getSeparationEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "") + config.getSeparationEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "")
+ "</separationevent>"); + "</separationevent>");
elements.add((indent ? " " : "") + "<separationaltitude>" + config.getSeparationAltitude() + "</separationaltitude>");
elements.add((indent ? " " : "") + "<separationdelay>" + config.getSeparationDelay() + "</separationdelay>"); elements.add((indent ? " " : "") + "<separationdelay>" + config.getSeparationDelay() + "</separationdelay>");
return elements; return elements;

View File

@ -4,6 +4,7 @@ import info.openrocket.core.l10n.Translator;
import info.openrocket.core.simulation.FlightEvent; import info.openrocket.core.simulation.FlightEvent;
import info.openrocket.core.startup.Application; import info.openrocket.core.startup.Application;
import info.openrocket.core.util.MathUtil; import info.openrocket.core.util.MathUtil;
import info.openrocket.core.util.Pair;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -12,22 +13,17 @@ import java.util.Objects;
public class StageSeparationConfiguration implements FlightConfigurableParameter<StageSeparationConfiguration> { public class StageSeparationConfiguration implements FlightConfigurableParameter<StageSeparationConfiguration> {
public static enum SeparationEvent { public static enum SeparationEvent {
//// Upper stage motor ignition //// Launch
UPPER_IGNITION(trans.get("Stage.SeparationEvent.UPPER_IGNITION")) { LAUNCH(trans.get("Stage.SeparationEvent.LAUNCH")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.IGNITION) return e.getType() == FlightEvent.Type.LAUNCH;
return false;
int ignition = e.getSource().getStageNumber();
int mount = stage.getStageNumber();
return (mount == ignition + 1);
} }
}, },
//// Current stage motor ignition //// Current stage motor ignition
IGNITION(trans.get("Stage.SeparationEvent.IGNITION")) { IGNITION(trans.get("Stage.SeparationEvent.IGNITION")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.IGNITION) if (e.getType() != FlightEvent.Type.IGNITION)
return false; return false;
@ -39,7 +35,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
//// Current stage motor burnout //// Current stage motor burnout
BURNOUT(trans.get("Stage.SeparationEvent.BURNOUT")) { BURNOUT(trans.get("Stage.SeparationEvent.BURNOUT")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.BURNOUT) if (e.getType() != FlightEvent.Type.BURNOUT)
return false; return false;
@ -51,7 +47,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
//// Current stage ejection charge //// Current stage ejection charge
EJECTION(trans.get("Stage.SeparationEvent.EJECTION")) { EJECTION(trans.get("Stage.SeparationEvent.EJECTION")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
if (e.getType() != FlightEvent.Type.EJECTION_CHARGE) if (e.getType() != FlightEvent.Type.EJECTION_CHARGE)
return false; return false;
@ -60,17 +56,58 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
return (mount == ignition); return (mount == ignition);
} }
}, },
//// Launch //// Upper stage motor ignition
LAUNCH(trans.get("Stage.SeparationEvent.LAUNCH")) { UPPER_IGNITION(trans.get("Stage.SeparationEvent.UPPER_IGNITION")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
return e.getType() == FlightEvent.Type.LAUNCH; 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<Double, Double> altitude = (Pair<Double, Double>) 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<Double, Double> altitude = (Pair<Double, Double>) e.getData();
return (altitude.getU() >= alt) && (altitude.getV() <= alt);
}
},
//// Never //// Never
NEVER(trans.get("Stage.SeparationEvent.NEVER")) { NEVER(trans.get("Stage.SeparationEvent.NEVER")) {
@Override @Override
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) { public boolean isSeparationEvent(StageSeparationConfiguration config, FlightEvent e, AxialStage stage) {
return false; return false;
} }
}, },
@ -85,8 +122,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
/** /**
* Test whether a specific event is a stage separation event. * 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 @Override
public String toString() { public String toString() {
return description; return description;
@ -96,6 +132,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private SeparationEvent separationEvent = SeparationEvent.EJECTION; private SeparationEvent separationEvent = SeparationEvent.EJECTION;
private double separationAltitude = 200;
private double separationDelay = 0; private double separationDelay = 0;
private final List<StageSeparationConfiguration> configListeners = new LinkedList<>(); private final List<StageSeparationConfiguration> configListeners = new LinkedList<>();
@ -119,6 +156,21 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
fireChangeEvent(); 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() { public double getSeparationDelay() {
return separationDelay; return separationDelay;
} }
@ -152,6 +204,7 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
public StageSeparationConfiguration copy(final FlightConfigurationId copyId) { public StageSeparationConfiguration copy(final FlightConfigurationId copyId) {
StageSeparationConfiguration clone = new StageSeparationConfiguration(); StageSeparationConfiguration clone = new StageSeparationConfiguration();
clone.separationEvent = this.separationEvent; clone.separationEvent = this.separationEvent;
clone.separationAltitude = this.separationAltitude;
clone.separationDelay = this.separationDelay; clone.separationDelay = this.separationDelay;
return clone; return clone;
} }

View File

@ -364,7 +364,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
continue; continue;
StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(this.fcid); 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, currentStatus.addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION,
event.getTime() + separationConfig.getSeparationDelay(), stage)); event.getTime() + separationConfig.getSeparationDelay(), stage));
} }

View File

@ -1192,6 +1192,7 @@ ComponentCfgDlg.ModifyComponents = Modify components
ComponentAssemblyConfig.tab.Separation = Separation ComponentAssemblyConfig.tab.Separation = Separation
ComponentAssemblyConfig.tab.Separation.ttip = Stage separation options ComponentAssemblyConfig.tab.Separation.ttip = Stage separation options
ComponentAssemblyConfig.separation.lbl.title = Select when this stage separates: 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.plus = plus
ComponentAssemblyConfig.separation.lbl.seconds = seconds ComponentAssemblyConfig.separation.lbl.seconds = seconds
ComponentAssemblyConfig.parallel.radius = Radial Distance: ComponentAssemblyConfig.parallel.radius = Radial Distance:
@ -1892,11 +1893,14 @@ Transition.Transition = Transition
!Stage !Stage
Stage.Stage = 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.IGNITION = Current stage motor ignition
Stage.SeparationEvent.BURNOUT = Current stage motor burnout Stage.SeparationEvent.BURNOUT = Current stage motor burnout
Stage.SeparationEvent.EJECTION = Current stage ejection charge 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 Stage.SeparationEvent.NEVER = Never
BoosterSet.BoosterSet = Booster Set BoosterSet.BoosterSet = Booster Set

View File

@ -13,6 +13,7 @@ import javax.swing.JDialog;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JRadioButton; import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JSpinner; import javax.swing.JSpinner;
import info.openrocket.core.formatting.RocketDescriptor; 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.SpinnerEditor;
import info.openrocket.swing.gui.adaptors.DoubleModel; import info.openrocket.swing.gui.adaptors.DoubleModel;
import info.openrocket.swing.gui.adaptors.EnumModel; 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.util.GUIUtil;
import info.openrocket.swing.gui.widgets.SelectColorButton; import info.openrocket.swing.gui.widgets.SelectColorButton;
@ -41,6 +44,11 @@ public class SeparationSelectionDialog extends JDialog {
private StageSeparationConfiguration newConfiguration; private StageSeparationConfiguration newConfiguration;
private final JLabel altText;
private final JSpinner altSpinner;
private final UnitSelector altUnit;
private final JSlider altSlider;
private boolean isOverrideDefault; private boolean isOverrideDefault;
public SeparationSelectionDialog(Window parent, final Rocket rocket, final AxialStage stage, FlightConfigurationId id) { public SeparationSelectionDialog(Window parent, final Rocket rocket, final AxialStage stage, FlightConfigurationId id) {
@ -53,7 +61,6 @@ public class SeparationSelectionDialog extends JDialog {
JPanel panel = new JPanel(new MigLayout("fill")); JPanel panel = new JPanel(new MigLayout("fill"));
// Select separation event // Select separation event
panel.add(new JLabel(trans.get("SeparationSelectionDialog.opt.title")), "span, wrap rel"); panel.add(new JLabel(trans.get("SeparationSelectionDialog.opt.title")), "span, wrap rel");
@ -78,9 +85,27 @@ public class SeparationSelectionDialog extends JDialog {
overrideButton.setSelected(true); overrideButton.setSelected(true);
} }
//// Separation
//// Stages separate at:
panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.SeparatesAt")), "");
final JComboBox<SeparationEvent> event = new JComboBox<SeparationEvent>(new EnumModel<SeparationEvent>(newConfiguration, "SeparationEvent")); final JComboBox<SeparationEvent> event = new JComboBox<SeparationEvent>(new EnumModel<SeparationEvent>(newConfiguration, "SeparationEvent"));
event.setSelectedItem(newConfiguration.getSeparationEvent()); 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 // ... and delay
panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.plus")), "alignx 100%"); panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.plus")), "alignx 100%");
@ -93,6 +118,13 @@ public class SeparationSelectionDialog extends JDialog {
//// seconds //// seconds
panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.seconds")), "wrap para"); 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"); panel.add(new JPanel(), "span, split, growx");
@ -131,6 +163,17 @@ public class SeparationSelectionDialog extends JDialog {
GUIUtil.installEscapeCloseButtonOperation(this, okButton); 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. * Returns true if this dialog was used to override the default configuration.
* @return true if this dialog was used to override the default configuration. * @return true if this dialog was used to override the default configuration.