diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocumentFactory.java b/core/src/net/sf/openrocket/document/OpenRocketDocumentFactory.java index eefef3e8e..24b4f6f13 100644 --- a/core/src/net/sf/openrocket/document/OpenRocketDocumentFactory.java +++ b/core/src/net/sf/openrocket/document/OpenRocketDocumentFactory.java @@ -15,6 +15,7 @@ public class OpenRocketDocumentFactory { //// Sustainer stage.setName(trans.get("BasicFrame.StageName.Sustainer")); rocket.addChild(stage); + rocket.getSelectedConfiguration().setAllStages(); OpenRocketDocument doc = new OpenRocketDocument(rocket); doc.setSaved(true); return doc; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java index 5aaa42cc3..1b0a4a2dd 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java @@ -20,6 +20,7 @@ class MotorConfigurationHandler extends AbstractElementHandler { private final Rocket rocket; private String name = null; private boolean inNameElement = false; + private HashMap stageActiveness = new HashMap<>(); public MotorConfigurationHandler(Rocket rocket, DocumentLoadingContext context) { this.rocket = rocket; @@ -30,11 +31,13 @@ class MotorConfigurationHandler extends AbstractElementHandler { public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) { - if (inNameElement || !element.equals("name")) { + if ((inNameElement && element.equals("name")) || !(element.equals("name") || element.equals("stage"))) { warnings.add(Warning.FILE_INVALID_PARAMETER); return null; } - inNameElement = true; + if (element.equals("name")) { + inNameElement = true; + } return PlainTextHandler.INSTANCE; } @@ -42,7 +45,13 @@ class MotorConfigurationHandler extends AbstractElementHandler { @Override public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) { - name = content; + if (element.equals("name")) { + name = content; + } else if (element.equals("stage")) { + int stageNr = Integer.parseInt(attributes.get("number")); + boolean isActive = Boolean.parseBoolean(attributes.get("active")); + stageActiveness.put(stageNr, isActive); + } } @Override @@ -60,7 +69,11 @@ class MotorConfigurationHandler extends AbstractElementHandler { if (name != null && name.trim().length() > 0) { rocket.getFlightConfiguration(fcid).setName(name); } - + + for (int stageNr : stageActiveness.keySet()) { + rocket.getFlightConfiguration(fcid).preloadStageActiveness(stageNr, stageActiveness.get(stageNr)); + } + if ("true".equals(attributes.remove("default"))) { rocket.setSelectedConfiguration( fcid); } diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 7662c80ad..5460d3c99 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; @@ -53,7 +54,10 @@ public class OpenRocketLoader extends AbstractRocketLoader { throw new RocketLoadException("Malformed XML in input.", e); } - doc.getSelectedConfiguration().setAllStages(); + // load the stage activeness + for (FlightConfiguration config : doc.getRocket().getFlightConfigurations()) { + config.applyPreloadedStageActiveness(); + } // Deduce suitable time skip double timeSkip = StorageOptions.SIMULATION_DATA_NONE; diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java index fb03fe8ee..b1e5f7cbd 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import net.sf.openrocket.file.openrocket.OpenRocketSaver; +import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.ReferenceType; @@ -53,15 +55,23 @@ public class RocketSaver extends RocketComponentSaver { if ( rocket.getSelectedConfiguration().equals( flightConfig )){ str += " default=\"true\""; } - + + // close motorconfiguration opening tag + str += ">"; + elements.add(str); + + // flight configuration name if (flightConfig.isNameOverridden()){ - str += ">" + net.sf.openrocket.util.TextUtil.escapeXML(flightConfig.getNameRaw()) - + ""; - } else { - str += "/>"; + elements.add(OpenRocketSaver.INDENT + "" + net.sf.openrocket.util.TextUtil.escapeXML(flightConfig.getNameRaw()) + + ""); + } + // stage activeness + for (AxialStage stage : rocket.getStageList()) { + elements.add(OpenRocketSaver.INDENT + ""); } - elements.add(str); + elements.add(""); } // Reference diameter diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 78ce41f66..2b58e7b64 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -64,6 +64,7 @@ public class FlightConfiguration implements FlightConfigurableParameter stages = new HashMap<>(); // Map of stage number to StageFlags of the corresponding stage final protected Map motors = new HashMap(); + private Map preloadStageActiveness = null; final private Collection activeMotors = new ConcurrentLinkedQueue(); final private InstanceMap activeInstances = new InstanceMap(); @@ -276,6 +277,34 @@ public class FlightConfiguration implements FlightConfigurableParameter(); + } + this.preloadStageActiveness.put(stageNumber, isActive); + } + + /** + * Applies preloaded stage activeness. + * This method should be called after the rocket has been loaded from a file. + */ + public void applyPreloadedStageActiveness() { + if (preloadStageActiveness == null) { + return; + } + for (int stageNumber : preloadStageActiveness.keySet()) { + _setStageActive(stageNumber, preloadStageActiveness.get(stageNumber), false); + } + preloadStageActiveness.clear(); + preloadStageActiveness = null; + } + public Collection getAllComponents() { List traversalOrder = new ArrayList(); recurseAllComponentsDepthFirst(this.rocket,traversalOrder); @@ -795,6 +824,7 @@ public class FlightConfiguration implements FlightConfigurableParameter(this.preloadStageActiveness); clone.cachedBoundsAerodynamic = this.cachedBoundsAerodynamic.clone(); clone.cachedBounds = this.cachedBounds.clone(); @@ -824,6 +854,7 @@ public class FlightConfiguration implements FlightConfigurableParameter(this.preloadStageActiveness); copy.cachedBoundsAerodynamic = this.cachedBoundsAerodynamic.clone(); copy.cachedBounds = this.cachedBounds.clone(); copy.modID = this.modID; diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index f7337b3e4..173c55268 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -752,23 +752,23 @@ public class Rocket extends ComponentAssembly { * Return a flight configuration. If the supplied id does not have a specific instance, the default is returned. * * @param fcid the flight configuration id - * @return FlightConfiguration instance + * @return FlightConfiguration instance */ - public FlightConfigurationId createFlightConfiguration( final FlightConfigurationId fcid) { + public FlightConfiguration createFlightConfiguration( final FlightConfigurationId fcid) { checkState(); if( null == fcid ){ // fall-through to the default case: // ...creating a FlightConfiguration( null ) just allocates a fresh new FCID }else if( fcid.hasError() ){ - return configSet.getDefault().getFlightConfigurationID(); + return configSet.getDefault(); }else if( configSet.containsId(fcid)){ - return fcid; + return configSet.get(fcid); } FlightConfiguration nextConfig = new FlightConfiguration(this, fcid); this.configSet.set(nextConfig.getId(), nextConfig); fireComponentChangeEvent(ComponentChangeEvent.TREE_CHANGE); - return nextConfig.getFlightConfigurationID(); + return nextConfig; } /** diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 6ac2328bd..44dc7b827 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -941,7 +941,7 @@ public class TestRockets { Rocket rocket = new Rocket(); rocket.setName("Falcon9H Scale Rocket"); - FlightConfigurationId selFCID = rocket.createFlightConfiguration( new FlightConfigurationId( FALCON_9H_FCID_1 )); + FlightConfigurationId selFCID = rocket.createFlightConfiguration( new FlightConfigurationId( FALCON_9H_FCID_1 )).getFlightConfigurationID(); rocket.setSelectedConfiguration(selFCID); // ====== Payload Stage ====== @@ -1625,6 +1625,14 @@ public class TestRockets { return document; } + + public static OpenRocketDocument makeTestRocket_v108_withDisabledStage() { + Rocket rocket = makeFalcon9Heavy(); + rocket.getSelectedConfiguration()._setStageActive(0, false, false); + OpenRocketDocument document = OpenRocketDocumentFactory.createDocumentFromRocket(rocket); + + return document; + } /* * Create a new test rocket for testing OpenRocketSaver.estimateFileSize() diff --git a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java index 0d5701d12..20abcbe4a 100644 --- a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java +++ b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.openrocket; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import java.io.File; @@ -19,6 +21,7 @@ import net.sf.openrocket.database.ComponentPresetDatabase; import net.sf.openrocket.database.motor.MotorDatabase; import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase; import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.OpenRocketDocumentFactory; import net.sf.openrocket.document.StorageOptions; import net.sf.openrocket.file.GeneralRocketLoader; import net.sf.openrocket.file.RocketLoadException; @@ -29,6 +32,10 @@ import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.plugin.PluginModule; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.extension.impl.ScriptingExtension; import net.sf.openrocket.simulation.extension.impl.ScriptingUtil; import net.sf.openrocket.startup.Application; @@ -126,6 +133,7 @@ public class OpenRocketSaverTest { rocketDocs.add(TestRockets.makeTestRocket_v106_withStageSeparationConfig()); rocketDocs.add(TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT)); rocketDocs.add(TestRockets.makeTestRocket_v108_withBoosters()); + rocketDocs.add(TestRockets.makeTestRocket_v108_withDisabledStage()); rocketDocs.add(TestRockets.makeTestRocket_for_estimateFileSize()); StorageOptions options = new StorageOptions(); @@ -138,6 +146,77 @@ public class OpenRocketSaverTest { assertNotNull(rocketDocLoaded); } } + + @Test + public void testSaveStageActiveness() { + OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v108_withDisabledStage(); + StorageOptions options = new StorageOptions(); + options.setSimulationTimeSkip(0.05); + + // Save rockets, load, validate + File file = saveRocket(rocketDoc, options); + OpenRocketDocument rocketDocLoaded = loadRocket(file.getPath()); + + // Check that the stages activeness is saved + FlightConfiguration config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + assertFalse(" selected config, stage 0 should have been disabled after saving", config.isStageActive(0)); + assertTrue(" selected config, stage 1 should have been enabled after saving", config.isStageActive(1)); + assertTrue(" selected config, stage 2 should have been enabled after saving", config.isStageActive(2)); + + // Disable second stage + config._setStageActive(1, false, false); + file = saveRocket(rocketDocLoaded, options); + rocketDocLoaded = loadRocket(file.getPath()); + config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + assertFalse(" selected config, stage 0 should have been disabled after saving", config.isStageActive(0)); + assertFalse(" selected config, stage 1 should have been disabled after saving", config.isStageActive(1)); + assertTrue(" selected config, stage 2 should have been enabled after saving", config.isStageActive(2)); + + // Re-enable first stage + config._setStageActive(0, true, false); + file = saveRocket(rocketDocLoaded, options); + rocketDocLoaded = loadRocket(file.getPath()); + config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + assertTrue(" selected config, stage 0 should have been enabled after saving", config.isStageActive(0)); + assertFalse(" selected config, stage 1 should have been disabled after saving", config.isStageActive(1)); + assertTrue(" selected config, stage 2 should have been enabled after saving", config.isStageActive(2)); + + // Check that other configurations are not affected + FlightConfiguration extraConfig = rocketDocLoaded.getRocket().createFlightConfiguration(TestRockets.TEST_FCID_0); + extraConfig.setAllStages(); + file = saveRocket(rocketDocLoaded, options); + rocketDocLoaded = loadRocket(file.getPath()); + config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + extraConfig = rocketDocLoaded.getRocket().getFlightConfiguration(TestRockets.TEST_FCID_0); + assertTrue(" selected config, stage 0 should have been enabled after saving", config.isStageActive(0)); + assertFalse(" selected config, stage 1 should have been disabled after saving", config.isStageActive(1)); + assertTrue(" selected config, stage 2 should have been enabled after saving", config.isStageActive(2)); + assertTrue(" extra config, stage 0 should have been enabled after saving", extraConfig.isStageActive(0)); + assertTrue(" extra config, stage 1 should have been enabled after saving", extraConfig.isStageActive(1)); + assertTrue(" extra config, stage 2 should have been enabled after saving", extraConfig.isStageActive(2)); + + // Disable a stage in the extra config, and an extra one in the selected config + extraConfig._setStageActive(0, false, false); + config._setStageActive(2, false, false); + file = saveRocket(rocketDocLoaded, options); + rocketDocLoaded = loadRocket(file.getPath()); + config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + extraConfig = rocketDocLoaded.getRocket().getFlightConfiguration(TestRockets.TEST_FCID_0); + assertTrue(" selected config, stage 0 should have been enabled after saving", config.isStageActive(0)); + assertFalse(" selected config, stage 1 should have been disabled after saving", config.isStageActive(1)); + assertFalse(" selected config, stage 2 should have been disabled after saving", config.isStageActive(2)); + assertFalse(" extra config, stage 0 should have been disabled after saving", extraConfig.isStageActive(0)); + assertTrue(" extra config, stage 1 should have been enabled after saving", extraConfig.isStageActive(1)); + assertTrue(" extra config, stage 2 should have been enabled after saving", extraConfig.isStageActive(2)); + + // Test an empty rocket with no configurations + OpenRocketDocument document = OpenRocketDocumentFactory.createNewRocket(); + file = saveRocket(document, options); + rocketDocLoaded = loadRocket(file.getPath()); + rocketDocLoaded.getRocket().getStage(0).addChild(new BodyTube()); // Add a child, otherwise the stage is always marked inactive + config = rocketDocLoaded.getRocket().getSelectedConfiguration(); + assertTrue(" empty rocket, selected config, stage 0 should have been enabled after saving", config.isStageActive(0)); + } @Test public void testUntrustedScriptDisabledOnLoad() { diff --git a/core/test/net/sf/openrocket/rocketcomponent/AxialStageTest.java b/core/test/net/sf/openrocket/rocketcomponent/AxialStageTest.java index e745df42c..32e576010 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/AxialStageTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/AxialStageTest.java @@ -16,7 +16,7 @@ public class AxialStageTest extends BaseTestCase { public void testDisableStage() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()); + final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()).getFlightConfigurationID(); final FlightConfiguration config2 = rocket.getFlightConfiguration(fcid); // Disable the payload stage @@ -106,7 +106,7 @@ public class AxialStageTest extends BaseTestCase { public void testDisableStageAndMove() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()); + final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()).getFlightConfigurationID(); final FlightConfiguration config2 = rocket.getFlightConfiguration(fcid); // Disable the payload stage @@ -173,7 +173,7 @@ public class AxialStageTest extends BaseTestCase { public void testDisableStageAndCopy() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()); + final FlightConfigurationId fcid = rocket.createFlightConfiguration(new FlightConfigurationId()).getFlightConfigurationID(); final FlightConfiguration config2 = rocket.getFlightConfiguration(fcid); // Disable the core stage diff --git a/fileformat.txt b/fileformat.txt index 6b7f385cb..2a488f6cc 100644 --- a/fileformat.txt +++ b/fileformat.txt @@ -57,6 +57,7 @@ The following file format versions exist: Transitions, Inner Tubes, Launch Lugs, and Fins. Added PhotoStudio settings saving () Added override CD parameter () + Added stage activeness remembrance ( under ) Separated into individual parameters for mass, CG, and CD. Rename to ( remains for backward compatibility) Rename to ( remains for backward compatibility) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index cf2455623..263ca0858 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -154,7 +154,6 @@ public class BasicFrame extends JFrame { this.document = document; this.rocket = document.getRocket(); - this.rocket.getSelectedConfiguration().setAllStages(); BasicFrame.lastFrameInstance = this; // Create the component tree selection model that will be used