From ef9a5d6c2bfcb01eec2cb63e49da0510d2cd264e Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 8 Aug 2024 20:35:08 -0600 Subject: [PATCH] Create stage separation conditions of specific altitude during ascent, apogee, and specific altitude during descent to support payloads with separate descent --- .../openrocket/importt/DocumentConfig.java | 3 + .../StageSeparationConfigurationHandler.java | 9 +- .../openrocket/savers/AxialStageSaver.java | 3 +- .../StageSeparationConfiguration.java | 91 +++++++++++++++---- .../BasicEventSimulationEngine.java | 4 +- .../main/resources/l10n/messages.properties | 8 +- .../SeparationSelectionDialog.java | 53 ++++++++++- 7 files changed, 141 insertions(+), 30 deletions(-) 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.