From bc8f72ca3c207c4772f583090f737c2cb4a229ff Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 27 Jun 2023 16:35:36 +0200 Subject: [PATCH 1/9] Save component ID for component + save flight event source This is to fix an issue where simulations loaded from the .ork file did not have the correct event source set --- .../file/openrocket/OpenRocketSaver.java | 9 +++++++-- .../openrocket/importt/DocumentConfig.java | 2 ++ .../importt/FlightDataBranchHandler.java | 18 +++++++++++++++++- .../savers/RocketComponentSaver.java | 1 + .../rocketcomponent/RocketComponent.java | 12 ++++++++++-- fileformat.txt | 3 +++ 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 9169348c6..b974de7a5 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -531,8 +531,13 @@ public class OpenRocketSaver extends RocketSaver { // Write events for (FlightEvent event : branch.getEvents()) { - writeln(""); + String eventStr = ""; + writeln(eventStr); } // Write the data diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java index 82428f119..dfecc84ab 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java @@ -113,6 +113,8 @@ class DocumentConfig { // RocketComponent setters.put("RocketComponent:name", new StringSetter( Reflection.findMethod(RocketComponent.class, "setName", String.class))); + setters.put("RocketComponent:id", new StringSetter( + Reflection.findMethod(RocketComponent.class, "setID", String.class))); setters.put("RocketComponent:color", new ColorSetter( Reflection.findMethod(RocketComponent.class, "setColor", Color.class))); setters.put("RocketComponent:linestyle", new EnumSetter( diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java index d44303c9b..8884cae8e 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java @@ -8,6 +8,8 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightEvent; @@ -126,6 +128,8 @@ class FlightDataBranchHandler extends AbstractElementHandler { if (element.equals("event")) { double time; FlightEvent.Type type; + String sourceID; + RocketComponent source = null; try { time = DocumentConfig.stringToDouble(attributes.get("time")); @@ -139,8 +143,20 @@ class FlightDataBranchHandler extends AbstractElementHandler { warnings.add("Illegal event specification, ignoring."); return; } + + // Get the event source + Rocket rocket = context.getOpenRocketDocument().getRocket(); + sourceID = attributes.get("source"); + if (sourceID != null) { + for (RocketComponent child : rocket.getAllChildren()) { + if (child.getID().equals(sourceID)) { + source = child; + break; + } + } + } - branch.addEvent(new FlightEvent(type, time)); + branch.addEvent(new FlightEvent(type, time, source)); return; } diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java index 04d031cd9..0bc98071c 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java @@ -36,6 +36,7 @@ public class RocketComponentSaver { protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { elements.add("" + TextUtil.escapeXML(c.getName()) + ""); + elements.add("" + TextUtil.escapeXML(c.getID()) + ""); ComponentPreset preset = c.getPresetComponent(); if (preset != null) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 01b5b6820..4241f4fd0 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1206,8 +1206,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab mutex.verify(); this.id = UniqueID.uuid(); } - - + + /** + * Set the ID for this component. + * Generally not recommended to directly set the ID, this is done automatically. Only use this in case you have to. + * @param newID new ID + */ + public void setID(String newID) { + mutex.verify(); + this.id = newID; + } /** diff --git a/fileformat.txt b/fileformat.txt index dafe8745a..aa3093182 100644 --- a/fileformat.txt +++ b/fileformat.txt @@ -63,3 +63,6 @@ The following file format versions exist: Rename to ( remains for backward compatibility) Rename to ( remains for backward compatibility) Rename to ( remains for backward compatibility) + +1.9: Introduced with OpenRocket 23.xx. + Added ID for each rocket component, to in turn add this ID as a source for flight events. \ No newline at end of file From 82b9ab89c7aeac1bc9dde8cc917b4bb283d6ec15 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 27 Jun 2023 16:39:56 +0200 Subject: [PATCH 2/9] [#2225] Copy simulation data from sustainer to other stages for CSV export --- .../src/net/sf/openrocket/file/CSVExport.java | 66 ++++++++++++++++--- .../rocketcomponent/RocketComponent.java | 10 +++ .../sf/openrocket/simulation/FlightData.java | 8 ++- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/core/src/net/sf/openrocket/file/CSVExport.java b/core/src/net/sf/openrocket/file/CSVExport.java index b8e1cc4a1..5e6a59419 100644 --- a/core/src/net/sf/openrocket/file/CSVExport.java +++ b/core/src/net/sf/openrocket/file/CSVExport.java @@ -11,6 +11,7 @@ import java.util.List; import net.sf.openrocket.logging.Warning; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -74,7 +75,7 @@ public class CSVExport { writer.println(); } - writeData(writer, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, + writeData(writer, simulation, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, eventComments, commentStarter); @@ -89,28 +90,77 @@ public class CSVExport { } } - private static void writeData(PrintWriter writer, FlightDataBranch branch, + private static void writeData(PrintWriter writer, Simulation simulation, FlightDataBranch branch, FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, boolean eventComments, String commentStarter) { + final FlightData data = simulation.getSimulatedData(); + final int stageNr = data.getStageNr(branch); + AxialStage stage = simulation.getRocket().getStage(stageNr); + + // Time variable + List time = branch.get(FlightDataType.TYPE_TIME); + + // Extra stages don't contain all the simulation data. Instead, the data is stored in the sustainer stage + // (since the data is the same for all stages until there is separation). + // For CSV export however, we want to copy the data from the sustainer stage to the extra stage. + boolean copySustainerData = stageNr > 0 && time != null && time.get(0) > 0; + FlightDataBranch sustainerBranch = null; + Double firstBranchTime = null; + int lastSustainerIndex = 0; + if (copySustainerData) { + firstBranchTime = time.get(0); + sustainerBranch = data.getBranch(0); + List sustainerTime = sustainerBranch.get(FlightDataType.TYPE_TIME); + + if (sustainerTime != null) { + for (int i = 0; i < sustainerTime.size(); i++) { + if (sustainerTime.get(i) >= firstBranchTime) { + lastSustainerIndex = i - 1; + break; + } + } + + List timeToCopy = sustainerTime.subList(0, lastSustainerIndex + 1); + time.addAll(0, timeToCopy); + } + } // Number of data points - int n = branch.getLength(); + int n = time != null ? time.size() : branch.getLength(); // Flight events in occurrence order List events = branch.getEvents(); + if (copySustainerData) { + List sustainerEvents = sustainerBranch.getEvents(); + + // Copy all events from the sustainer that belong to this stage + for (FlightEvent event : sustainerEvents) { + // Stage separation is present both in the sustainer data and extra stage data (don't really know why...) + if (event.getType() == FlightEvent.Type.STAGE_SEPARATION) { + continue; + } + if (stage == event.getSource() || stage.containsChild(event.getSource())) { + events.add(event); + } + } + } Collections.sort(events); int eventPosition = 0; // List of field values - List> fieldValues = new ArrayList>(); + List> fieldValues = new ArrayList<>(); for (FlightDataType t : fields) { - fieldValues.add(branch.get(t)); + List values = branch.get(t); + if (copySustainerData) { + List sustainerValues = sustainerBranch.get(t); + List valuesToCopy = sustainerValues.subList(0, lastSustainerIndex + 1); + values.addAll(0, valuesToCopy); + } + fieldValues.add(values); } - // Time variable - List time = branch.get(FlightDataType.TYPE_TIME); + // If time information is not available, print events at beginning of file if (eventComments && time == null) { - // If time information is not available, print events at beginning of file for (FlightEvent e : events) { printEvent(writer, e, commentStarter); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 4241f4fd0..73e6b8c61 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1979,6 +1979,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } return children; } + + /** + * Checks whether this component contains as one of its (sub-)children. + * @param component component to check + * @return true if component is a (sub-)child of this component + */ + public final boolean containsChild(RocketComponent component) { + List allChildren = getAllChildren(); + return allChildren.contains(component); + } /** diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index 7a559b3f2..5d6f62017 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -134,8 +134,12 @@ public class FlightData { return branches.size(); } - public FlightDataBranch getBranch(int n) { - return branches.get(n); + public FlightDataBranch getBranch(int stageNr) { + return branches.get(stageNr); + } + + public int getStageNr(FlightDataBranch branch) { + return branches.indexOf(branch); } public List getBranches() { From 8f3fe0e44aabf8129170c1d9102cff466244ad63 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 28 Jun 2023 15:52:27 +0200 Subject: [PATCH 3/9] Use existing method for finding component with ID --- .../file/openrocket/importt/FlightDataBranchHandler.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java index 8884cae8e..9641f13d6 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java @@ -148,12 +148,7 @@ class FlightDataBranchHandler extends AbstractElementHandler { Rocket rocket = context.getOpenRocketDocument().getRocket(); sourceID = attributes.get("source"); if (sourceID != null) { - for (RocketComponent child : rocket.getAllChildren()) { - if (child.getID().equals(sourceID)) { - source = child; - break; - } - } + source = rocket.findComponent(sourceID); } branch.addEvent(new FlightEvent(type, time, source)); From 7ec6d985447b1826cd87f113852835ee7eebba8d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Jul 2023 13:36:54 +0200 Subject: [PATCH 4/9] Update file version --- .../sf/openrocket/file/openrocket/OpenRocketSaver.java | 9 +++++---- .../file/openrocket/importt/DocumentConfig.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index b974de7a5..048db84c4 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -216,21 +216,22 @@ public class OpenRocketSaver extends RocketSaver { /* * NOTE: Remember to update the supported versions in DocumentConfig as well! * - * File version 1.8 is required for: + * File version 1.9 is required for: * - new-style positioning * - external/parallel booster stages * - external pods * - Rail Buttons + * - Flight event source saving * - * Otherwise use version 1.8. + * Otherwise use version 1.9. */ ///////////////// - // Version 1.8 // + // Version 1.9 // ///////////////// // for any new-style positioning: 'axialoffset', 'angleoffset', 'radiusoffset' tags // these tags are used for any RocketComponent child classes positioning... so... ALL the classes. - return FILE_VERSION_DIVISOR + 8; + return FILE_VERSION_DIVISOR + 9; } diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java index dfecc84ab..6995c8210 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java @@ -52,7 +52,7 @@ import net.sf.openrocket.util.Reflection; class DocumentConfig { /* Remember to update OpenRocketSaver as well! */ - public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8" }; + public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9" }; /** * Divisor used in converting an integer version to the point-represented version. From 40598f1c8c528136ce942fb08070a60f1b0068e3 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Jul 2023 13:48:08 +0200 Subject: [PATCH 5/9] Update unit test --- .../sf/openrocket/file/openrocket/OpenRocketSaverTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java index 0eae42619..d7ccef0fb 100644 --- a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java +++ b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java @@ -331,9 +331,9 @@ public class OpenRocketSaverTest { //////////////////////////////// @Test - public void testFileVersion108_withSimulationExtension() { + public void testFileVersion109_withSimulationExtension() { OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT); - assertEquals(108, getCalculatedFileVersion(rocketDoc)); + assertEquals(109, getCalculatedFileVersion(rocketDoc)); } From e9146a35d4d29be61a6cdaeabe36a57f0819a3d6 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 23 Jul 2023 02:18:33 +0200 Subject: [PATCH 6/9] Move flight data secondary data copying to constructor --- .../src/net/sf/openrocket/file/CSVExport.java | 53 +------- .../BasicEventSimulationEngine.java | 2 +- .../simulation/FlightDataBranch.java | 119 +++++++++++++----- .../openrocket/gui/plot/SimulationPlot.java | 20 +-- 4 files changed, 92 insertions(+), 102 deletions(-) diff --git a/core/src/net/sf/openrocket/file/CSVExport.java b/core/src/net/sf/openrocket/file/CSVExport.java index 5e6a59419..886ebc235 100644 --- a/core/src/net/sf/openrocket/file/CSVExport.java +++ b/core/src/net/sf/openrocket/file/CSVExport.java @@ -11,7 +11,6 @@ import java.util.List; import net.sf.openrocket.logging.Warning; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -75,7 +74,7 @@ public class CSVExport { writer.println(); } - writeData(writer, simulation, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, + writeData(writer, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, eventComments, commentStarter); @@ -90,60 +89,17 @@ public class CSVExport { } } - private static void writeData(PrintWriter writer, Simulation simulation, FlightDataBranch branch, + private static void writeData(PrintWriter writer, FlightDataBranch branch, FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, boolean eventComments, String commentStarter) { - final FlightData data = simulation.getSimulatedData(); - final int stageNr = data.getStageNr(branch); - AxialStage stage = simulation.getRocket().getStage(stageNr); - // Time variable List time = branch.get(FlightDataType.TYPE_TIME); - // Extra stages don't contain all the simulation data. Instead, the data is stored in the sustainer stage - // (since the data is the same for all stages until there is separation). - // For CSV export however, we want to copy the data from the sustainer stage to the extra stage. - boolean copySustainerData = stageNr > 0 && time != null && time.get(0) > 0; - FlightDataBranch sustainerBranch = null; - Double firstBranchTime = null; - int lastSustainerIndex = 0; - if (copySustainerData) { - firstBranchTime = time.get(0); - sustainerBranch = data.getBranch(0); - List sustainerTime = sustainerBranch.get(FlightDataType.TYPE_TIME); - - if (sustainerTime != null) { - for (int i = 0; i < sustainerTime.size(); i++) { - if (sustainerTime.get(i) >= firstBranchTime) { - lastSustainerIndex = i - 1; - break; - } - } - - List timeToCopy = sustainerTime.subList(0, lastSustainerIndex + 1); - time.addAll(0, timeToCopy); - } - } - // Number of data points int n = time != null ? time.size() : branch.getLength(); // Flight events in occurrence order List events = branch.getEvents(); - if (copySustainerData) { - List sustainerEvents = sustainerBranch.getEvents(); - - // Copy all events from the sustainer that belong to this stage - for (FlightEvent event : sustainerEvents) { - // Stage separation is present both in the sustainer data and extra stage data (don't really know why...) - if (event.getType() == FlightEvent.Type.STAGE_SEPARATION) { - continue; - } - if (stage == event.getSource() || stage.containsChild(event.getSource())) { - events.add(event); - } - } - } Collections.sort(events); int eventPosition = 0; @@ -151,11 +107,6 @@ public class CSVExport { List> fieldValues = new ArrayList<>(); for (FlightDataType t : fields) { List values = branch.get(t); - if (copySustainerData) { - List sustainerValues = sustainerBranch.get(t); - List valuesToCopy = sustainerValues.subList(0, lastSustainerIndex + 1); - values.addAll(0, valuesToCopy); - } fieldValues.add(values); } diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index ffdb3c2fa..be9fd8bc6 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -489,7 +489,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { SimulationStatus boosterStatus = new SimulationStatus(currentStatus); // Prepare the new simulation branch - boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), currentStatus.getFlightData())); + boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), boosterStage, currentStatus.getFlightData())); boosterStatus.getFlightData().addEvent(event); // Mark the current status as having dropped the current stage and all stages below it diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java index e2547e4c4..270431deb 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -6,6 +6,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.Monitorable; import net.sf.openrocket.util.Mutable; @@ -30,11 +31,10 @@ public class FlightDataBranch implements Monitorable { /** The name of this flight data branch. */ private final String branchName; - private final Map> values = - new LinkedHashMap>(); + private final Map> values = new LinkedHashMap<>(); - private final Map maxValues = new HashMap(); - private final Map minValues = new HashMap(); + private final Map maxValues = new HashMap<>(); + private final Map minValues = new HashMap<>(); /** * time for the rocket to reach apogee if the flight had been no recovery deployment @@ -77,23 +77,19 @@ public class FlightDataBranch implements Monitorable { } /** - * Make a flight data branch with one data point copied from its parent. Intended for use + * Make a flight data branch with all data points copied from its parent. Intended for use * when creating a new branch upon stage separation, so the data at separation is present * in both branches (and if the new branch has an immediate exception, it can be plotted) + * + * @param branchName the name of the new branch. + * @param srcComponent the component that is the source of the new branch. + * @param parent the parent branch to copy data from. */ - public FlightDataBranch(String branchName, FlightDataBranch parent) { + public FlightDataBranch(String branchName, RocketComponent srcComponent, FlightDataBranch parent) { this.branchName = branchName; - // need to have at least one type to set up values - values.put(FlightDataType.TYPE_TIME, new ArrayList()); - minValues.put(FlightDataType.TYPE_TIME, Double.NaN); - maxValues.put(FlightDataType.TYPE_TIME, Double.NaN); - - // copy all values into new FlightDataBranch - this.addPoint(); - for (FlightDataType t : parent.getTypes()) { - this.setValue(t, parent.getLast(t)); - } + // Copy all the values from the parent + copyValuesFromBranch(parent, srcComponent); } /** @@ -115,12 +111,27 @@ public class FlightDataBranch implements Monitorable { public void addPoint() { mutable.check(); - for (FlightDataType t : values.keySet()) { - values.get(t).add(Double.NaN); + for (FlightDataType type : values.keySet()) { + sanityCheckValues(type, Double.NaN); + values.get(type).add(Double.NaN); } modID++; } - + + private void sanityCheckValues(FlightDataType type, Double value) { + ArrayList list = values.get(type); + + if (list == null) { + list = new ArrayList<>(); + int n = getLength(); + for (int i = 0; i < n; i++) { + list.add(Double.NaN); + } + values.put(type, list); + minValues.put(type, value); + maxValues.put(type, value); + } + } /** * Set the value for a specific data type at the latest point. New variable types can be @@ -132,20 +143,10 @@ public class FlightDataBranch implements Monitorable { */ public void setValue(FlightDataType type, double value) { mutable.check(); - + + sanityCheckValues(type, value); ArrayList list = values.get(type); - if (list == null) { - list = new ArrayList(); - int n = getLength(); - for (int i = 0; i < n; i++) { - list.add(Double.NaN); - } - values.put(type, list); - minValues.put(type, value); - maxValues.put(type, value); - } - if (list.size() > 0) { list.set(list.size() - 1, value); } @@ -161,7 +162,44 @@ public class FlightDataBranch implements Monitorable { } modID++; } - + + /** + * Clears all the current values in the branch and copies the values from the given branch. + * @param srcBranch the branch to copy values from + * @param srcComponent the component that is the source of this branch (used for copying events) + */ + private void copyValuesFromBranch(FlightDataBranch srcBranch, RocketComponent srcComponent) { + this.values.clear(); + + // Need to have at least one type to set up values + values.put(FlightDataType.TYPE_TIME, new ArrayList<>()); + minValues.put(FlightDataType.TYPE_TIME, Double.NaN); + maxValues.put(FlightDataType.TYPE_TIME, Double.NaN); + + if (srcBranch == null) { + return; + } + + // Copy flight data + for (int i = 0; i < srcBranch.getLength(); i++) { + this.addPoint(); + for (FlightDataType type : srcBranch.getTypes()) { + this.setValue(type, srcBranch.getByIndex(type, i)); + } + } + + // Copy flight events belonging to this branch + List sustainerEvents = srcBranch.getEvents(); + for (FlightEvent event : sustainerEvents) { + // Stage separation is already added elsewhere, so don't copy it over (otherwise you have a duplicate) + if (event.getType() == FlightEvent.Type.STAGE_SEPARATION) { + continue; + } + if (srcComponent != null && (srcComponent == event.getSource() || srcComponent.containsChild(event.getSource()))) { + events.add(event); + } + } + } /** * Return the branch name. @@ -203,6 +241,23 @@ public class FlightDataBranch implements Monitorable { return null; return list.clone(); } + + /** + * Return the value of the specified type at the specified index. + * @param type the variable type + * @param index the data index of the value + * @return the value at the specified index + */ + public Double getByIndex(FlightDataType type, int index) { + if (index < 0 || index >= getLength()) { + throw new IllegalArgumentException("Index out of bounds"); + } + ArrayList list = values.get(type); + if (list == null) { + return null; + } + return list.get(index); + } /** * Return the last value of the specified type in the branch, or NaN if the type is diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java index 57f45c09c..e30b01646 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java @@ -192,9 +192,8 @@ public class SimulationPlot { } data[axis].addSeries(series); } - // For each of the secondary branches, we use data from branch 0 for the earlier times + // Secondary branches for (int branchIndex = 1; branchIndex < branchCount; branchIndex++) { - FlightDataBranch primaryBranch = simulation.getSimulatedData().getBranch(0); FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchIndex); // Ignore empty branches @@ -206,25 +205,10 @@ public class SimulationPlot { continue; } - // Get first time index used in secondary branch; - double firstSampleTime = thisBranch.get(FlightDataType.TYPE_TIME).get(0); - XYSeries series = new XYSeries(seriesCount++, false, true); series.setDescription(thisBranch.getBranchName() + ": " + name); - // Copy the first points from the primaryBranch. - List primaryT = primaryBranch.get(FlightDataType.TYPE_TIME); - List primaryx = primaryBranch.get(domainType); - List primaryy = primaryBranch.get(type); - - for (int j = 0; j < primaryT.size(); j++) { - if (primaryT.get(j) >= firstSampleTime) { - break; - } - series.add(domainUnit.toUnit(primaryx.get(j)), unit.toUnit(primaryy.get(j))); - } - - // Now copy all the data from the secondary branch + // Copy all the data from the secondary branch List plotx = thisBranch.get(domainType); List ploty = thisBranch.get(type); From b6235a16302aae17738034e92800aca5181b33a8 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 23 Jul 2023 02:53:19 +0200 Subject: [PATCH 7/9] Don't copy flight events of child boosters --- .../simulation/FlightDataBranch.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java index 270431deb..395c96343 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -6,6 +6,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.Monitorable; @@ -195,11 +197,35 @@ public class FlightDataBranch implements Monitorable { if (event.getType() == FlightEvent.Type.STAGE_SEPARATION) { continue; } - if (srcComponent != null && (srcComponent == event.getSource() || srcComponent.containsChild(event.getSource()))) { + RocketComponent srcEventComponent = event.getSource(); + // Ignore null events + if (srcComponent == null || srcEventComponent == null) { + continue; + } + // Ignore events from other stages. Important for when the current stage has a booster stage; we don't want to copy over the booster events. + if (getStageForComponent(srcComponent) != getStageForComponent(srcEventComponent)) { + continue; + } + if (srcComponent == srcEventComponent || srcComponent.containsChild(srcEventComponent)) { events.add(event); } } } + + /** + * A safer method for checking the stage of a component (that shouldn't throw exceptions when calling on stages/rockets) + * @param component the component to get the stage of + * @return the stage of the component, or null if the component is a rocket + */ + private AxialStage getStageForComponent(RocketComponent component) { + if (component instanceof AxialStage) { + return (AxialStage) component; + } else if (component instanceof Rocket) { + return null; + } else { + return component.getStage(); + } + } /** * Return the branch name. From 84ff186d39e1ebcc789f2b7fb5bdbb3ec2e6db01 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 23 Jul 2023 02:53:26 +0200 Subject: [PATCH 8/9] Update unit tests --- .../openrocket/simulation/FlightEventsTest.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java index e4fa4d305..8820f2710 100644 --- a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java +++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java @@ -86,6 +86,7 @@ public class FlightEventsTest extends BaseTestCase { public void testMultiStage() throws SimulationException { final Rocket rocket = TestRockets.makeFalcon9Heavy(); TestRockets.addCoreFins(rocket); + TestRockets.dumpRocket(rocket, "/Users/SiboVanGool/Downloads/f9.ork"); final Simulation sim = new Simulation(rocket); sim.getOptions().setISAAtmosphere(true); @@ -108,8 +109,8 @@ public class FlightEventsTest extends BaseTestCase { // events whose time is too variable to check are given a time of 1200 for (int b = 0; b < 3; b++) { FlightEvent[] expectedEvents; - final RocketComponent[] expectedSources; switch (b) { + // Sustainer (payload fairing stage) case 0: expectedEvents = new FlightEvent[] { new FlightEvent(FlightEvent.Type.LAUNCH, 0.0, rocket), @@ -129,15 +130,23 @@ public class FlightEventsTest extends BaseTestCase { new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null) }; break; + // Core stage case 1: expectedEvents = new FlightEvent[] { + new FlightEvent(FlightEvent.Type.IGNITION, 0.0, coreBody), + new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, coreBody), + new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, coreStage), new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, coreStage), new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null), new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null) }; break; + // Booster stage case 2: expectedEvents = new FlightEvent[] { + new FlightEvent(FlightEvent.Type.IGNITION, 0.0, boosterMotorTubes), + new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, boosterMotorTubes), + new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, boosterStage), new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage), new FlightEvent(FlightEvent.Type.TUMBLE, 3.551, null), new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null), @@ -150,10 +159,7 @@ public class FlightEventsTest extends BaseTestCase { // Test event count final FlightDataBranch branch = sim.getSimulatedData().getBranch(b); - final FlightEvent[] events = (FlightEvent[]) branch.getEvents().toArray(new FlightEvent[0]); - for (int i = 0; i < events.length; i++) { - System.out.println("branch " + b + " index " + i + " event " + events[i]); - } + final FlightEvent[] events = branch.getEvents().toArray(new FlightEvent[0]); assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events ", expectedEvents.length, events.length); // Test that all expected events are present, in the right order, at the right time, from the right sources From 9c0466d54351420a9638e89d7cda4e0d4afe926c Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sun, 23 Jul 2023 15:37:15 +0200 Subject: [PATCH 9/9] Whoops, remove rocket dump --- core/test/net/sf/openrocket/simulation/FlightEventsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java index 8820f2710..5340eefc0 100644 --- a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java +++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java @@ -86,7 +86,6 @@ public class FlightEventsTest extends BaseTestCase { public void testMultiStage() throws SimulationException { final Rocket rocket = TestRockets.makeFalcon9Heavy(); TestRockets.addCoreFins(rocket); - TestRockets.dumpRocket(rocket, "/Users/SiboVanGool/Downloads/f9.ork"); final Simulation sim = new Simulation(rocket); sim.getOptions().setISAAtmosphere(true);