From b1b16907736d13e09f6f0bff8db788ba969dbbb2 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Fri, 19 Jul 2024 09:15:42 -0600 Subject: [PATCH 01/20] little bit of cleanup -- use result of test for errors in each branch to determine whether to report a simulation error, instead of duplicating the code --- .../src/main/java/info/openrocket/core/document/Simulation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/info/openrocket/core/document/Simulation.java b/core/src/main/java/info/openrocket/core/document/Simulation.java index 3becacabe..f85e10369 100644 --- a/core/src/main/java/info/openrocket/core/document/Simulation.java +++ b/core/src/main/java/info/openrocket/core/document/Simulation.java @@ -356,7 +356,7 @@ public class Simulation implements ChangeSource, Cloneable { public boolean hasErrors() { FlightData data = getSimulatedData(); for (int branchNo = 0; branchNo < data.getBranchCount(); branchNo++) { - if (data.getBranch(branchNo).getFirstEvent(FlightEvent.Type.SIM_ABORT) != null) { + if (hasErrors(branchNo)) { return true; } } From bbf38dd2392c2264754d2db9d81d6fea0759a45e Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Mon, 22 Jul 2024 06:16:49 -0600 Subject: [PATCH 02/20] Create SIM_WARN FlightEvent to log warnings raised during simulation. Translations of FlightEvent.Type.SIM_WARN to languages other than English were created by copying RocketInfo.Warning for each language in which it exists --- .../info/openrocket/core/simulation/FlightEvent.java | 11 +++++++++++ core/src/main/resources/l10n/messages.properties | 1 + core/src/main/resources/l10n/messages_ar.properties | 1 + core/src/main/resources/l10n/messages_de.properties | 1 + core/src/main/resources/l10n/messages_es.properties | 1 + core/src/main/resources/l10n/messages_fr.properties | 1 + core/src/main/resources/l10n/messages_it.properties | 1 + core/src/main/resources/l10n/messages_ja.properties | 1 + core/src/main/resources/l10n/messages_nl.properties | 1 + core/src/main/resources/l10n/messages_pl.properties | 1 + core/src/main/resources/l10n/messages_pt.properties | 1 + core/src/main/resources/l10n/messages_ru.properties | 1 + .../src/main/resources/l10n/messages_uk_UA.properties | 1 + .../src/main/resources/l10n/messages_zh_CN.properties | 1 + 14 files changed, 24 insertions(+) diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java index 3e7f80b49..5802738b3 100644 --- a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java +++ b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java @@ -2,6 +2,7 @@ package info.openrocket.core.simulation; import info.openrocket.core.l10n.Translator; import info.openrocket.core.logging.SimulationAbort; +import info.openrocket.core.logging.Warning; import info.openrocket.core.rocketcomponent.AxialStage; import info.openrocket.core.rocketcomponent.MotorMount; import info.openrocket.core.rocketcomponent.RocketComponent; @@ -81,6 +82,11 @@ public class FlightEvent implements Comparable { */ TUMBLE(trans.get("FlightEvent.Type.TUMBLE")), + /** + * A warning was raised during the execution of the simulation + */ + SIM_WARN(trans.get("FlightEvent.Type.SIM_WARN")), + /** * It is impossible for the simulation proceed due to characteristics * of the rocket or flight configuration @@ -241,6 +247,11 @@ public class FlightEvent implements Comparable { } } break; + case SIM_WARN: + if (( null == this.data ) || ( ! ( this.data instanceof Warning ))) { + throw new IllegalStateException(type.name()+" events require Warning objects"); + } + break; case SIM_ABORT: if (( null == this.data ) || ( ! ( this.data instanceof SimulationAbort ))) { throw new IllegalStateException(type.name()+" events require SimulationAbort objects"); diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 679525eac..d37baf6aa 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -2026,6 +2026,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stage separation FlightEvent.Type.APOGEE = Apogee FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment FlightEvent.Type.GROUND_HIT = Ground hit +FlightEvent.Type.SIM_WARN = Warning FlightEvent.Type.SIM_ABORT = Simulation abort FlightEvent.Type.SIMULATION_END = Simulation end FlightEvent.Type.ALTITUDE = Altitude change diff --git a/core/src/main/resources/l10n/messages_ar.properties b/core/src/main/resources/l10n/messages_ar.properties index b2b80f334..c1909fc2f 100644 --- a/core/src/main/resources/l10n/messages_ar.properties +++ b/core/src/main/resources/l10n/messages_ar.properties @@ -1705,6 +1705,7 @@ FlightEvent.Type.STAGE_SEPARATION = فصل المرحلة FlightEvent.Type.APOGEE = الأوج FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = نشر جهاز الإسترداد FlightEvent.Type.GROUND_HIT = الإصطدام بالأرض +FlightEvent.Type.SIM_WARN = تحذير FlightEvent.Type.SIMULATION_END = نهاية المحاكاة FlightEvent.Type.ALTITUDE = تغيير الإرتفاع FlightEvent.Type.TUMBLE = هبوط diff --git a/core/src/main/resources/l10n/messages_de.properties b/core/src/main/resources/l10n/messages_de.properties index 102db8ece..884de7704 100644 --- a/core/src/main/resources/l10n/messages_de.properties +++ b/core/src/main/resources/l10n/messages_de.properties @@ -1259,6 +1259,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stufentrennung FlightEvent.Type.APOGEE = Apogum FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Auslsung des Bergungssystems FlightEvent.Type.GROUND_HIT = Landung +FlightEvent.Type.SIM_WARN = Warnung FlightEvent.Type.SIMULATION_END = Ende der Simulation FlightEvent.Type.ALTITUDE = Hhennderung diff --git a/core/src/main/resources/l10n/messages_es.properties b/core/src/main/resources/l10n/messages_es.properties index cacc19f7e..34ee25533 100644 --- a/core/src/main/resources/l10n/messages_es.properties +++ b/core/src/main/resources/l10n/messages_es.properties @@ -350,6 +350,7 @@ FlightEvent.Type.LAUNCH = Lanzamiento FlightEvent.Type.LAUNCHROD = Abandono de la Gu\u00eda de lanzamiento FlightEvent.Type.LIFTOFF = Despegue FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Despliegue del sistema de recuperaci\u00f3n +FlightEvent.Type.SIM_WARN = Peligro, cohete inestable FlightEvent.Type.SIMULATION_END = Fin de la simulaci\u00f3n FlightEvent.Type.STAGE_SEPARATION = Separaci\u00f3n de etapa FlightEvent.Type.TUMBLE = Volteo diff --git a/core/src/main/resources/l10n/messages_fr.properties b/core/src/main/resources/l10n/messages_fr.properties index 41c03ef7c..97f61a95b 100644 --- a/core/src/main/resources/l10n/messages_fr.properties +++ b/core/src/main/resources/l10n/messages_fr.properties @@ -341,6 +341,7 @@ FlightEvent.Type.LAUNCH = Lancement FlightEvent.Type.LAUNCHROD = D\u00E9gagement de rampe de lancement FlightEvent.Type.LIFTOFF = D\u00E9collage FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = D\u00E9ploiement du dispositif de r\u00E9cup\u00E9ration +FlightEvent.Type.SIM_WARN = Avertissement FlightEvent.Type.SIMULATION_END = Fin de la simulation FlightEvent.Type.STAGE_SEPARATION = S\u00E9paration d'\u00E9tage FlightEvent.Type.TUMBLE = D\u00E9gringolade diff --git a/core/src/main/resources/l10n/messages_it.properties b/core/src/main/resources/l10n/messages_it.properties index 67271b83b..a5de1023a 100644 --- a/core/src/main/resources/l10n/messages_it.properties +++ b/core/src/main/resources/l10n/messages_it.properties @@ -1263,6 +1263,7 @@ FlightEvent.Type.STAGE_SEPARATION = Separazione degli stadi FlightEvent.Type.APOGEE = Apogeo FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = distacco del dispositivo di recupero FlightEvent.Type.GROUND_HIT = Atterraggio +FlightEvent.Type,SIM_WARN = Avvisi FlightEvent.Type.SIMULATION_END = Fine simulazione FlightEvent.Type.ALTITUDE = Cambio altitudine diff --git a/core/src/main/resources/l10n/messages_ja.properties b/core/src/main/resources/l10n/messages_ja.properties index 6bd18b21b..6d900fe52 100644 --- a/core/src/main/resources/l10n/messages_ja.properties +++ b/core/src/main/resources/l10n/messages_ja.properties @@ -1310,6 +1310,7 @@ FlightEvent.Type.STAGE_SEPARATION = \u30B9\u30C6\u30FC\u30B8\u5206\u96E2 FlightEvent.Type.APOGEE = \u6700\u9AD8\u5230\u9054\u70B9 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = \u30EA\u30AB\u30D0\u30EA\u30FC\u88C5\u7F6E\u5C55\u958B FlightEvent.Type.GROUND_HIT = \u7740\u5730 +FlightEvent.Type.SIM_WARN = \u30A8\u30E9\u30FC FlightEvent.Type.SIMULATION_END = \u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3\u7D42\u4E86 FlightEvent.Type.ALTITUDE = \u59FF\u52E2\u5909\u66F4 diff --git a/core/src/main/resources/l10n/messages_nl.properties b/core/src/main/resources/l10n/messages_nl.properties index cc8da870e..668718cf9 100644 --- a/core/src/main/resources/l10n/messages_nl.properties +++ b/core/src/main/resources/l10n/messages_nl.properties @@ -1628,6 +1628,7 @@ FlightEvent.Type.STAGE_SEPARATION = Trap afscheiding FlightEvent.Type.APOGEE = Apogee FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Terugvordering toestel uitrol FlightEvent.Type.GROUND_HIT = Grond geraakt +FlightEvent.Type.SIM_WARN = Waarschuwing FlightEvent.Type.SIMULATION_END = Simulatie-einde FlightEvent.Type.ALTITUDE = Hoogteverandering FlightEvent.Type.TUMBLE = Tuimelen diff --git a/core/src/main/resources/l10n/messages_pl.properties b/core/src/main/resources/l10n/messages_pl.properties index e8419cd44..62c137beb 100644 --- a/core/src/main/resources/l10n/messages_pl.properties +++ b/core/src/main/resources/l10n/messages_pl.properties @@ -1204,6 +1204,7 @@ ComponentInfo.EngineBlock = Blokada silnika unieruchamia silnik wewn\u01 FlightEvent.Type.APOGEE = Apogeum FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Aktywacja uk\u0142adu odzysku FlightEvent.Type.GROUND_HIT = Uderzenie w ziemi\u0119 + FlightEvent.Type.SIM_WARN = Ostrze\u017Cenie FlightEvent.Type.SIMULATION_END = Koniec symulacji FlightEvent.Type.ALTITUDE = Zmiana wysoko\u015Bci diff --git a/core/src/main/resources/l10n/messages_pt.properties b/core/src/main/resources/l10n/messages_pt.properties index 1ae20d6f2..d16a1e07f 100644 --- a/core/src/main/resources/l10n/messages_pt.properties +++ b/core/src/main/resources/l10n/messages_pt.properties @@ -330,6 +330,7 @@ FlightEvent.Type.LAUNCH = Lan\u00e7amento FlightEvent.Type.LAUNCHROD = Folga da haste de lan\u00e7amento FlightEvent.Type.LIFTOFF = Decolagem FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Implanta\u00e7\u00e3o de dispositivos de recupera\u00e7\u00e3o +FlightEvent.Type.SIM_WARN = Alerta FlightEvent.Type.SIMULATION_END = Final de simula\u00e7\u00e3o FlightEvent.Type.STAGE_SEPARATION = Separa\u00e7\u00e3o do est\u00e1gio FlightEvent.Type.TUMBLE = Tumbling diff --git a/core/src/main/resources/l10n/messages_ru.properties b/core/src/main/resources/l10n/messages_ru.properties index 30e644adf..51b54e2cd 100644 --- a/core/src/main/resources/l10n/messages_ru.properties +++ b/core/src/main/resources/l10n/messages_ru.properties @@ -1677,6 +1677,7 @@ FlightEvent.Type.STAGE_SEPARATION = \u0420\u0430\u0437\u0434\u0435\u043B\u0435\u FlightEvent.Type.APOGEE = \u0410\u043F\u043E\u0433\u0435\u0439 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = \u0421\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0441\u043F\u0430\u0441\u0435\u043D\u0438\u044F FlightEvent.Type.GROUND_HIT = \u041F\u0440\u0438\u0437\u0435\u043C\u043B\u0435\u043D\u0438\u0435 +FlightEvent.Type.SIM_WARN = \u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435 FlightEvent.Type.SIMULATION_END = \u041A\u043E\u043D\u0435\u0446 \u0440\u0430\u0441\u0447\u0435\u0442\u0430 FlightEvent.Type.ALTITUDE = \u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u0432\u044B\u0441\u043E\u0442\u044B FlightEvent.Type.TUMBLE = \u041E\u043F\u0440\u043E\u043A\u0438\u0434\u044B\u0432\u0430\u043D\u0438\u0435 diff --git a/core/src/main/resources/l10n/messages_uk_UA.properties b/core/src/main/resources/l10n/messages_uk_UA.properties index a7c2c7d09..9a48396a7 100644 --- a/core/src/main/resources/l10n/messages_uk_UA.properties +++ b/core/src/main/resources/l10n/messages_uk_UA.properties @@ -1401,6 +1401,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stage separation FlightEvent.Type.APOGEE = Apogee FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment FlightEvent.Type.GROUND_HIT = Ground hit +FlightEvent.Type.SIM_WARN = Warning FlightEvent.Type.SIMULATION_END = Simulation end FlightEvent.Type.ALTITUDE = Altitude change FlightEvent.Type.TUMBLE = Tumbling diff --git a/core/src/main/resources/l10n/messages_zh_CN.properties b/core/src/main/resources/l10n/messages_zh_CN.properties index 81cf30543..3037edecd 100644 --- a/core/src/main/resources/l10n/messages_zh_CN.properties +++ b/core/src/main/resources/l10n/messages_zh_CN.properties @@ -367,6 +367,7 @@ FlightEvent.Type.LAUNCH = \u53D1\u5C04 FlightEvent.Type.LAUNCHROD = \u79BB\u67B6 FlightEvent.Type.LIFTOFF = \u8D77\u98DE FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = \u56DE\u6536\u88C5\u7F6E\u542F\u52A8 +FlightEvent.Type.SIM_WARN = \u8B66\u544A FlightEvent.Type.SIMULATION_END = \u4EFF\u771F\u7ED3\u675F FlightEvent.Type.STAGE_SEPARATION = \u7EA7\u95F4\u5206\u79BB FlightEvent.Type.TUMBLE = \u7FFB\u6EDA From 47e7e017bbddc4f04d9f1c42fd3e0919c2f37451 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Mon, 22 Jul 2024 10:55:28 -0600 Subject: [PATCH 03/20] Create new SimulationStatus.addWarning() and SimulationStatus.addWarnings() methods. addWarning() adds a warning to the WarningSet, and adds a SIM_WARN FlightEvent to the data branch. addWarnings() uses addWarning() to merge two WarningSets. Replace instances of status.getWarnings().add() (and similar code) with status.addWarning() --- .../simulation/BasicEventSimulationEngine.java | 12 ++++++------ .../core/simulation/RK4SimulationStepper.java | 6 ++++-- .../core/simulation/SimulationStatus.java | 18 ++++++++++++++++++ .../listeners/SimulationListenerHelper.java | 2 +- 4 files changed, 29 insertions(+), 9 deletions(-) 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..6ee1f5cb9 100644 --- a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java +++ b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java @@ -108,7 +108,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { // No recovery device if (!simulationConfig.hasRecoveryDevice()) { - currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE); + currentStatus.addWarning(Warning.NO_RECOVERY_DEVICE); } currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket())); @@ -315,7 +315,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { if (currentStatus.isLanded() && (event.getType() != FlightEvent.Type.ALTITUDE) && (event.getType() != FlightEvent.Type.SIMULATION_END)) - currentStatus.getWarnings().add(new Warning.EventAfterLanding(event)); + currentStatus.addWarning(new Warning.EventAfterLanding(event)); // Check for motor ignition events, add ignition events to queue for (MotorClusterState state : currentStatus.getActiveMotors() ){ @@ -500,12 +500,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { } } if (numActiveBelow != 1) { - currentStatus.getWarnings().add(Warning.SEPARATION_ORDER); + currentStatus.addWarning(Warning.SEPARATION_ORDER); } // If I haven't cleared the rail yet, flag a warning if (!currentStatus.isLaunchRodCleared()) { - currentStatus.getWarnings().add(Warning.EARLY_SEPARATION); + currentStatus.addWarning(Warning.EARLY_SEPARATION); } // Create a new simulation branch for the booster @@ -565,12 +565,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Check for launch rod if (!currentStatus.isLaunchRodCleared()) { - currentStatus.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD); + currentStatus.addWarning(Warning.RECOVERY_LAUNCH_ROD); } // Check current velocity if (currentStatus.getRocketVelocity().length() > 20) { - currentStatus.getWarnings().add(new Warning.HighSpeedDeployment(currentStatus.getRocketVelocity().length())); + currentStatus.addWarning(new Warning.HighSpeedDeployment(currentStatus.getRocketVelocity().length())); } currentStatus.setLiftoff(true); diff --git a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java index 889e74d17..892c5000c 100644 --- a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java @@ -403,7 +403,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { * launch rod or 0.25 seconds after departure, and when the velocity has dropped * below 20% of the max. velocity. */ - WarningSet warnings = status.getWarnings(); + WarningSet warnings = new WarningSet(); store.maxZvelocity = MathUtil.max(store.maxZvelocity, status.getRocketVelocity().z); if (!status.isLaunchRodCleared()) { @@ -423,7 +423,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { // Calculate aerodynamic forces store.forces = status.getSimulationConditions().getAerodynamicCalculator() .getAerodynamicForces(status.getConfiguration(), store.flightConditions, warnings); - + if (null != warnings) { + status.addWarnings(warnings); + } // Add very small randomization to yaw & pitch moments to prevent over-perfect flight // TODO: HIGH: This should rather be performed as a listener diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java index 953becf09..36f3d5512 100644 --- a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java +++ b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java @@ -9,6 +9,7 @@ import java.util.Set; import info.openrocket.core.aerodynamics.FlightConditions; import info.openrocket.core.logging.SimulationAbort; +import info.openrocket.core.logging.Warning; import info.openrocket.core.logging.WarningSet; import info.openrocket.core.motor.MotorConfiguration; import info.openrocket.core.motor.MotorConfigurationId; @@ -420,6 +421,23 @@ public class SimulationStatus implements Cloneable, Monitorable { this.warnings = warnings; } + public void addWarning(Warning warning) { + if (null == warnings) { + setWarnings(new WarningSet()); + } + if (!warnings.contains(warning)) { + log.trace("Add warning: \"" + warning + "\""); + getFlightDataBranch().addEvent(new FlightEvent(FlightEvent.Type.SIM_WARN, getSimulationTime(), null, warning)); + warnings.add(warning); + } + } + + public void addWarnings(WarningSet warnings) { + for (Warning warning : warnings) { + addWarning(warning); + } + } + public WarningSet getWarnings() { return warnings; } diff --git a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java index 0b5ca9597..bd8ffb8b7 100644 --- a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java +++ b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java @@ -636,7 +636,7 @@ public class SimulationListenerHelper { private static void warn(SimulationStatus status, SimulationListener listener) { if (!listener.isSystemListener()) { log.info("Non-system listener " + listener + " affected the simulation"); - status.getWarnings().add(Warning.LISTENERS_AFFECTED); + status.addWarning(Warning.LISTENERS_AFFECTED); } } } From b6fafb995c71b96ec603a982781e11ae4d70fe92 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Wed, 31 Jul 2024 15:41:09 -0600 Subject: [PATCH 04/20] starting to plot warning events. Not done yet --- .../swing/gui/plot/EventGraphics.java | 52 ++++++++++++------- .../swing/gui/plot/SimulationPlot.java | 7 ++- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java index 992a7affa..ceb5fea55 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java @@ -9,6 +9,8 @@ import java.util.Map; import javax.imageio.ImageIO; +import info.openrocket.core.logging.MessagePriority; +import info.openrocket.core.logging.Warning; import info.openrocket.core.simulation.FlightEvent; public class EventGraphics { @@ -20,9 +22,14 @@ public class EventGraphics { return DEFAULT_EVENT_COLOR; } - static Image getEventImage(FlightEvent.Type type ) { - Image i = EVENT_IMAGES.get(type); - return i; + static Image getEventImage(FlightEvent event) { + + FlightEvent.Type type = event.getType(); + if (type == FlightEvent.Type.SIM_WARN) { + return MESSAGE_IMAGES.get(((Warning) event.getData()).getPriority()); + } else { + return EVENT_IMAGES.get(type); + } } private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0); @@ -46,24 +53,32 @@ public class EventGraphics { private static final Map EVENT_IMAGES = new HashMap(); static { - loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png"); - loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png"); - loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png"); - loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png"); - loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png"); - loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png"); - loadImage(FlightEvent.Type.STAGE_SEPARATION, + loadImage(EVENT_IMAGES, FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.STAGE_SEPARATION, "pix/eventicons/event-stage-separation.png"); - loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png"); - loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, + loadImage(EVENT_IMAGES, FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, "pix/eventicons/event-recovery-device-deployment.png"); - loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png"); - loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png"); - loadImage(FlightEvent.Type.EXCEPTION, "pix/eventicons/event-exception.png"); - loadImage(FlightEvent.Type.SIM_ABORT, "pix/eventicons/event-exception.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.EXCEPTION, "pix/eventicons/event-exception.png"); + loadImage(EVENT_IMAGES, FlightEvent.Type.SIM_ABORT, "pix/eventicons/event-exception.png"); + } + + // Messages can happen at several priority levels, requiring different icons + private static final Map MESSAGE_IMAGES = new HashMap(); + static { + loadImage(MESSAGE_IMAGES, MessagePriority.LOW, "pix/icons/warning_low.png"); + loadImage(MESSAGE_IMAGES, MessagePriority.NORMAL, "pix/icons/warning_normal.png"); + loadImage(MESSAGE_IMAGES, MessagePriority.HIGH, "pix/icons/warning_high.png"); } - private static void loadImage(FlightEvent.Type type, String file) { + private static void loadImage(Map imageMap, KeyType type, String file) { InputStream is; is = ClassLoader.getSystemResourceAsStream(file); @@ -74,10 +89,9 @@ public class EventGraphics { try { Image image = ImageIO.read(is); - EVENT_IMAGES.put(type, image); + imageMap.put(type, image); } catch (IOException ignore) { ignore.printStackTrace(); } } - } diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 53b7f3336..b0879d666 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -286,7 +286,6 @@ public class SimulationPlot { } XYSeries ser = collection.getSeries(series); String name = ser.getDescription(); - // Extract the unit from the last part of the series description, between parenthesis Matcher m = Pattern.compile(".*\\((.*?)\\)").matcher(name); String unitY = ""; @@ -409,7 +408,6 @@ public class SimulationPlot { private void drawDomainMarkers(int branch) { XYPlot plot = chart.getXYPlot(); FlightDataBranch dataBranch = simulation.getSimulatedData().getBranch(Math.max(branch, 0)); - // Clear existing domain markers and annotations plot.clearDomainMarkers(); plot.clearAnnotations(); @@ -453,6 +451,7 @@ public class SimulationPlot { Color color = null; Image image = null; int maxOrdinal = -1; + for (EventDisplayInfo info : eventList) { if (branch >= 0 && branch != info.stage) { continue; @@ -465,7 +464,7 @@ public class SimulationPlot { text = text + ", " + type.toString(); if (type.ordinal() > maxOrdinal) { color = EventGraphics.getEventColor(type); - image = EventGraphics.getEventImage(type); + image = EventGraphics.getEventImage(info.event); maxOrdinal = type.ordinal(); } typeSet.add(type); @@ -481,7 +480,7 @@ public class SimulationPlot { prevTime = t; text = type.toString(); color = EventGraphics.getEventColor(type); - image = EventGraphics.getEventImage(type); + image = EventGraphics.getEventImage(info.event); typeSet.clear(); typeSet.add(type); maxOrdinal = type.ordinal(); From 1d4ef4383aef63029d77c7906b968ed1834e33e0 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 22 Aug 2024 16:50:43 -0600 Subject: [PATCH 05/20] SIM_WARN events take a null source. The Warning being passed in includes a set of sources; in case of a SIM_WARN event we'll look the source up there --- .../info/openrocket/core/simulation/FlightEvent.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java index 5802738b3..921dfdf05 100644 --- a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java +++ b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java @@ -124,8 +124,8 @@ public class FlightEvent implements Comparable { this(type, time, source, null); } - public FlightEvent(final FlightEvent _sourceEvent, final RocketComponent _comp, final Object _data) { - this(_sourceEvent.type, _sourceEvent.time, _comp, _data); + public FlightEvent( final FlightEvent sourceEvent, final RocketComponent source, final Object data) { + this(sourceEvent.type, sourceEvent.time, source, data); } public FlightEvent( final Type type, final double time, final RocketComponent source, final Object data) { @@ -248,6 +248,12 @@ public class FlightEvent implements Comparable { } break; case SIM_WARN: + if (null != this.source) { + // rather than making event sources take sets of components, or trying to keep them + // in sync with the sources of Warnings, we'll require the event source to be null + // and pull the actual sources from the Warning + throw new IllegalStateException(type.name()+" event requires null source component; was " + this.source); + } if (( null == this.data ) || ( ! ( this.data instanceof Warning ))) { throw new IllegalStateException(type.name()+" events require Warning objects"); } From fad20af87932a3cbf829c8411c6932da2fd8bedf Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Fri, 30 Aug 2024 20:26:19 -0600 Subject: [PATCH 06/20] Refer to Warning UUID as ID for consistency with other code --- .../info/openrocket/core/logging/Message.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/info/openrocket/core/logging/Message.java b/core/src/main/java/info/openrocket/core/logging/Message.java index 5f97fa01c..8de8254c9 100644 --- a/core/src/main/java/info/openrocket/core/logging/Message.java +++ b/core/src/main/java/info/openrocket/core/logging/Message.java @@ -1,17 +1,30 @@ package info.openrocket.core.logging; -import info.openrocket.core.rocketcomponent.RocketComponent; import java.util.Arrays; +import java.util.UUID; + +import info.openrocket.core.rocketcomponent.RocketComponent; /** * Baseclass for logging messages (warnings, errors...) */ public abstract class Message implements Cloneable { + /** Message ID **/ + UUID id; + /** The rocket component(s) that are the source of this message **/ private RocketComponent[] sources = null; private MessagePriority priority = MessagePriority.NORMAL; + protected Message() { + this.id = UUID.randomUUID(); + } + + protected Message(UUID id) { + this.id = id; + } + /** * Returns the message text + message source objects. * @return the message text + message source objects. @@ -54,6 +67,13 @@ public abstract class Message implements Cloneable { */ public abstract boolean replaceBy(Message other); + /** + * Return the ID + */ + public UUID getID() { + return id; + } + /** * Return the rocket component(s) that are the source of this warning. * @return the rocket component(s) that are the source of this warning. Returns null if no sources are specified. From 12f8802cd3e6c773296788e597dc06c6e62376cb Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Mon, 9 Sep 2024 06:27:53 -0600 Subject: [PATCH 07/20] Load and store SIM_WARN flight events This requires substantial changes to Warnings in .ork files. Instead of Warnings consisting of a text string with id and priority as attributes, it is now a mixed content element with id, priority, description, and possibly sources as subelements and the original text string still present to provide backward compatibility. If a .ork file with a warning is saved with this PR and then loaded also with this PR, the warning is reconstructed from the subelements and is available if there is a SIM_WARN flight event. If at some point we want to highlight the components referenced in a Warning, we now have references to them. If a .ork file with a warning is saved with 23.09 and then loaded with this PR, none of the subelements are present, so the Warning is reconstructed from the text. It won't have actual references to the components mentioned in the Warning, and there won't be a SIM_WARN event needing reference the Warning. If a .ork file with a warning is saved with this PR and then loaded in 23.09, there will be .ork loading warnings for all the elements defined in this PR, and then the Warning will be constructed from the text content just as for a .ork saved in 23.09. --- .../core/file/openrocket/OpenRocketSaver.java | 40 ++++++---- .../importt/FlightDataBranchHandler.java | 22 ++++-- .../openrocket/importt/FlightDataHandler.java | 17 +++-- .../importt/SingleSimulationHandler.java | 7 ++ .../openrocket/importt/WarningHandler.java | 73 +++++++++++++++++++ .../core/file/simplesax/ElementHandler.java | 2 +- .../info/openrocket/core/logging/Message.java | 7 ++ .../openrocket/core/logging/MessageSet.java | 21 ++++-- .../info/openrocket/core/logging/Warning.java | 5 +- 9 files changed, 158 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java b/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java index cc841799d..982df34bc 100644 --- a/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java +++ b/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java @@ -403,7 +403,24 @@ public class OpenRocketSaver extends RocketSaver { indent++; for (Warning w : data.getWarningSet()) { - writeElementWithAttribute("warning", "priority", w.getPriority().getExportLabel(), TextUtil.escapeXML(w.toString())); + writeln(""); + indent++; + + writeElement("id", w.getID().toString()); + writeElement("description", w.getMessageDescription()); + writeElement("priority", w.getPriority()); + + if (null != w.getSources()) { + for (RocketComponent c : w.getSources()) { + writeElement("source", c.getID()); + } + } + + // We write the whole string content for backwards compatibility with old versions + writeln(TextUtil.escapeXML(w.toString())); + + indent--; + writeln(""); } // Check whether to store data @@ -579,16 +596,21 @@ public class OpenRocketSaver extends RocketSaver { // Write events for (FlightEvent event : branch.getEvents()) { String eventStr = ""; + eventStr += "/>"; writeln(eventStr); } @@ -649,14 +671,6 @@ public class OpenRocketSaver extends RocketSaver { content = ""; writeln("<" + element + ">" + TextUtil.escapeXML(content) + ""); } - - private void writeElementWithAttribute(String element, String attributeName, String attribute, Object content) throws IOException { - content = content == null ? "" : content; - - writeln("<" + element + " " + attributeName + " = \"" + attribute + "\">" + TextUtil.escapeXML(content) + ""); - } - - private void writeln(String str) throws IOException { if (str.length() == 0) { diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java index 7ba4088b8..2b2277ce1 100644 --- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java +++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java @@ -3,6 +3,7 @@ package info.openrocket.core.file.openrocket.importt; import java.util.HashMap; import java.util.UUID; +import info.openrocket.core.logging.Message; import info.openrocket.core.logging.SimulationAbort; import info.openrocket.core.logging.SimulationAbort.Cause; import info.openrocket.core.logging.WarningSet; @@ -132,15 +133,14 @@ class FlightDataBranchHandler extends AbstractElementHandler { if (element.equals("event")) { double time; FlightEvent.Type type; - SimulationAbort abort = null; - SimulationAbort.Cause cause = null; + Message data = null; RocketComponent source = null; String sourceID; try { time = DocumentConfig.stringToDouble(attributes.get("time")); } catch (NumberFormatException e) { - warnings.add("Illegal event specification, ignoring."); + warnings.add("Illegal event time specification, ignoring: " + e.getMessage()); return; } @@ -157,13 +157,23 @@ class FlightDataBranchHandler extends AbstractElementHandler { source = rocket.findComponent(UUID.fromString(sourceID)); } + // For warning events, get the warning + if (type == FlightEvent.Type.SIM_WARN) { + data = simHandler.getWarningSet().findById(UUID.fromString(attributes.get("id"))); + } + // For aborts, get the cause - cause = (Cause) DocumentConfig.findEnum(attributes.get("cause"), SimulationAbort.Cause.class); + Cause cause = (Cause) DocumentConfig.findEnum(attributes.get("cause"), SimulationAbort.Cause.class); if (cause != null) { - abort = new SimulationAbort(cause); + data = new SimulationAbort(cause); } - branch.addEvent(new FlightEvent(type, time, source, abort)); + try { + branch.addEvent(new FlightEvent(type, time, source, data)); + } catch (Exception e) { + warnings.add("Illegal parameters for FlightEvent: " + e.getMessage()); + } + return; } diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java index 6ce1573f1..1c6ad1b89 100644 --- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java +++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java @@ -39,7 +39,7 @@ class FlightDataHandler extends AbstractElementHandler { WarningSet warnings) { if (element.equals("warning")) { - return PlainTextHandler.INSTANCE; + return new WarningHandler(context.getOpenRocketDocument().getRocket(), warningSet); } if (element.equals("databranch")) { if (attributes.get("name") == null || attributes.get("types") == null) { @@ -83,10 +83,10 @@ class FlightDataHandler extends AbstractElementHandler { if (branch.getLength() > 0) { branches.add(branch); } - } else if (element.equals("warning")) { - String priorityStr = attributes.get("priority"); - MessagePriority priority = MessagePriority.fromExportLabel(priorityStr); - warningSet.add(Warning.fromString(content, priority)); + // } else if (element.equals("warning")) { + // String priorityStr = attributes.get("priority"); + // MessagePriority priority = MessagePriority.fromExportLabel(priorityStr); + // warningSet.add(Warning.fromString(content, priority)); } } @@ -157,6 +157,9 @@ class FlightDataHandler extends AbstractElementHandler { data.getWarningSet().addAll(warningSet); data.immute(); } - - + + + public WarningSet getWarningSet() { + return warningSet; + } } diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java index f283144f1..80e062b03 100644 --- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java +++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java @@ -156,6 +156,13 @@ class SingleSimulationHandler extends AbstractElementHandler { doc.addSimulation(simulation); } + /** + * @return the warning set associated with this simulation + */ + public WarningSet getWarningSet() { + return dataHandler.getWarningSet(); + } + private SimulationExtension compatibilityExtension(String className) { JavaCode extension = Application.getInjector().getInstance(JavaCode.class); extension.setClassName(className); diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java new file mode 100644 index 000000000..edfde5bc5 --- /dev/null +++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java @@ -0,0 +1,73 @@ +package info.openrocket.core.file.openrocket.importt; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; + +import info.openrocket.core.file.simplesax.AbstractElementHandler; +import info.openrocket.core.file.simplesax.ElementHandler; +import info.openrocket.core.file.simplesax.PlainTextHandler; +import info.openrocket.core.logging.MessagePriority; +import info.openrocket.core.logging.Warning; +import info.openrocket.core.logging.WarningSet; +import info.openrocket.core.rocketcomponent.Rocket; +import info.openrocket.core.rocketcomponent.RocketComponent; + +class WarningHandler extends AbstractElementHandler { + private Rocket rocket; + private WarningSet warningSet; + private Warning warning; + private UUID id = UUID.randomUUID(); + private MessagePriority priority = MessagePriority.NORMAL; + private ArrayList sources = new ArrayList<>(); + private String warningText; + + public WarningHandler(Rocket rocket, WarningSet warningSet) { + this.rocket = rocket; + this.warningSet = warningSet; + } + + @Override + public ElementHandler openElement(String element, HashMap attributes, + WarningSet warnings) { + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) { + if (element.equals("id")) { + id = UUID.fromString(content); + } else if (element.equals("description")) { + warning = Warning.fromString(content); + } else if (element.equals("priority")) { + priority = MessagePriority.fromExportLabel(content); + } else if (element.equals("source")) { + RocketComponent component = rocket.findComponent(UUID.fromString(content)); + sources.add(component); + } else { + warnings.add("Unknown element '" + element + "', ignoring."); + } + } + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) { + + // If we didn't already create a warning, this came from an old version + if (null == warning) { + warning = Warning.fromString(content.trim()); + } + if (null != id) { + warning.setID(id); + } + if (null != priority) { + warning.setPriority(priority); + } + if (null != sources) { + warning.setSources(sources.toArray(new RocketComponent[0])); + } + + warningSet.add(warning); + } +} diff --git a/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java b/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java index 3f4e13326..38bb894ec 100644 --- a/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java +++ b/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java @@ -80,4 +80,4 @@ public interface ElementHandler { public abstract void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException; -} \ No newline at end of file +} diff --git a/core/src/main/java/info/openrocket/core/logging/Message.java b/core/src/main/java/info/openrocket/core/logging/Message.java index 8de8254c9..e7b9b46a1 100644 --- a/core/src/main/java/info/openrocket/core/logging/Message.java +++ b/core/src/main/java/info/openrocket/core/logging/Message.java @@ -73,6 +73,13 @@ public abstract class Message implements Cloneable { public UUID getID() { return id; } + + /** + * Set the ID + **/ + public void setID(UUID id) { + this.id = id; + } /** * Return the rocket component(s) that are the source of this warning. diff --git a/core/src/main/java/info/openrocket/core/logging/MessageSet.java b/core/src/main/java/info/openrocket/core/logging/MessageSet.java index be11b51d7..f71c8c039 100644 --- a/core/src/main/java/info/openrocket/core/logging/MessageSet.java +++ b/core/src/main/java/info/openrocket/core/logging/MessageSet.java @@ -1,5 +1,10 @@ package info.openrocket.core.logging; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.util.ArrayList; import info.openrocket.core.util.BugException; @@ -7,10 +12,6 @@ import info.openrocket.core.util.ModID; import info.openrocket.core.util.Monitorable; import info.openrocket.core.util.Mutable; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.List; - /** * A set that contains multiple Messages. When adding a * {@link Message} to this set, the contents is checked for a message of the @@ -69,7 +70,7 @@ public abstract class MessageSet extends AbstractSet imple * @param sources the sources of the message (rocket components that caused the message) * */ - public boolean add(E m, RocketComponent... sources) { + public boolean add(E m, RocketComponent... sources) { mutable.check(); try { m = (E) m.clone(); @@ -78,7 +79,7 @@ public abstract class MessageSet extends AbstractSet imple } m.setSources(sources); return add(m); - } + } /** * Add a Message of the specified type with the specified discriminator to the @@ -149,6 +150,14 @@ public abstract class MessageSet extends AbstractSet imple return list; } + public Message findById(UUID id) { + for (Message m : messages) { + if (m.id.equals(id)) + return m; + } + throw new BugException("Message with id " + id + " not found"); + } + public void immute() { mutable.immute(); } diff --git a/core/src/main/java/info/openrocket/core/logging/Warning.java b/core/src/main/java/info/openrocket/core/logging/Warning.java index 30bd9364b..7361f7180 100644 --- a/core/src/main/java/info/openrocket/core/logging/Warning.java +++ b/core/src/main/java/info/openrocket/core/logging/Warning.java @@ -18,7 +18,8 @@ public abstract class Warning extends Message { * @return a Message with the specific text and priority. */ public static Warning fromString(String text, MessagePriority priority) { - return new Warning.Other(text, priority); + Warning.Other warn = new Warning.Other(text, priority); + return warn; } /** @@ -27,8 +28,6 @@ public abstract class Warning extends Message { public static Warning fromString(String text) { return fromString(text, MessagePriority.NORMAL); } - - ///////////// Specific warning classes ///////////// From 67f6aa191af872d9834aaece803baae350f65f6c Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 12 Sep 2024 11:06:24 -0600 Subject: [PATCH 08/20] Plot SIM_WARN flight events --- .../info/openrocket/swing/gui/plot/EventGraphics.java | 5 +++-- .../info/openrocket/swing/gui/plot/SimulationPlot.java | 4 ++-- .../swing/gui/plot/SimulationPlotConfiguration.java | 10 ++++++++++ .../swing/gui/simulation/SimulationPlotPanel.java | 3 +-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java index 812c9886d..27005f4da 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java @@ -15,7 +15,8 @@ import info.openrocket.core.simulation.FlightEvent; public class EventGraphics { - static Color getEventColor(FlightEvent.Type type) { + static Color getEventColor(FlightEvent event) { + FlightEvent.Type type = event.getType(); Color c = EVENT_COLORS.get(type); if (c != null) return c; @@ -23,7 +24,6 @@ public class EventGraphics { } static Image getEventImage(FlightEvent event) { - FlightEvent.Type type = event.getType(); if (type == FlightEvent.Type.SIM_WARN) { return MESSAGE_IMAGES.get(((Warning) event.getData()).getPriority()); @@ -48,6 +48,7 @@ public class EventGraphics { EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0)); EVENT_COLORS.put(FlightEvent.Type.TUMBLE, new Color(196, 0, 255)); EVENT_COLORS.put(FlightEvent.Type.EXCEPTION, new Color(255, 0, 0)); + EVENT_COLORS.put(FlightEvent.Type.SIM_WARN, new Color(127, 127, 0)); EVENT_COLORS.put(FlightEvent.Type.SIM_ABORT, new Color(255, 0, 0)); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 0d02bc5ff..21dec76a6 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -183,7 +183,7 @@ public class SimulationPlot extends Plot maxOrdinal) { - color = EventGraphics.getEventColor(type); + color = EventGraphics.getEventColor(info.event); image = EventGraphics.getEventImage(info.event); maxOrdinal = type.ordinal(); } @@ -199,7 +199,7 @@ public class SimulationPlot extends Plot Date: Mon, 23 Sep 2024 06:53:22 -0600 Subject: [PATCH 09/20] Add SIM_WARN events to plots. Note that this required a lot of rewrite to the code adding event icons --- .../core/simulation/FlightEvent.java | 7 + .../info/openrocket/swing/gui/plot/Plot.java | 21 +- .../swing/gui/plot/SimulationPlot.java | 228 ++++++++++++------ 3 files changed, 183 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java index 921dfdf05..37b703f5a 100644 --- a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java +++ b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java @@ -183,6 +183,13 @@ public class FlightEvent implements Comparable { // finally, sort on event type return this.type.ordinal() - o.type.ordinal(); } + + public boolean equals(FlightEvent o) { + if ((this.type == Type.SIM_WARN) && (o.type == Type.SIM_WARN)) + return ((Warning)(this.data)).equals((Warning)(o.data)); + + return this.equals(0); + } @Override public String toString() { diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index ce9c532c3..d6730b401 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -101,8 +101,8 @@ public abstract class Plot, C extend // Create the data series for both axes this.data = new XYSeriesCollection[2]; - this.data[0] = new XYSeriesCollection(); - this.data[1] = new XYSeriesCollection(); + this.data[Util.PlotAxisSelection.LEFT.getValue()] = new XYSeriesCollection(); + this.data[Util.PlotAxisSelection.RIGHT.getValue()] = new XYSeriesCollection(); // Fill the auto-selections based on first branch selected. this.filledConfig = config.fillAutoAxes(mainBranch); @@ -278,7 +278,7 @@ public abstract class Plot, C extend protected List createSeriesForType(int dataIndex, int startIndex, T type, Unit unit, B branch, int branchIdx, String branchName, String baseName) { // Default implementation for regular DataBranch - MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, unit.getUnit(), branchName, baseName); + MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, dataIndex, unit.getUnit(), branchName, baseName); List plotx = branch.get(filledConfig.getDomainAxisType()); List ploty = branch.get(type); @@ -327,7 +327,7 @@ public abstract class Plot, C extend return formatSampleTooltip(dataName, dataX, unitX, 0, "", sampleIdx, false); } - private String getOrdinalEnding(int n) { + protected String getOrdinalEnding(int n) { if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) return "th"; return switch (n % 10) { case 1 -> "st"; @@ -540,20 +540,27 @@ public abstract class Plot, C extend protected static class MetadataXYSeries extends XYSeries { private final int branchIdx; + private final int dataIdx; private final String unit; private final String branchName; private String baseName; - public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit, + public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, int dataIdx, String unit, String branchName, String baseName) { super(key, autoSort, allowDuplicateXValues); this.branchIdx = branchIdx; + this.dataIdx = dataIdx; this.unit = unit; this.branchName = branchName; this.baseName = baseName; updateDescription(); } + public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit, + String branchName, String baseName) { + this(key, autoSort, allowDuplicateXValues, branchIdx, -1, unit, branchName, baseName); + } + public String getUnit() { return unit; } @@ -562,6 +569,10 @@ public abstract class Plot, C extend return branchIdx; } + public int getDataIdx() { + return dataIdx; + } + public String getBranchName() { return branchName; } diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 21dec76a6..041278370 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -5,6 +5,7 @@ import java.awt.Font; import java.awt.Image; import java.util.ArrayList; import java.util.Comparator; +import java.text.DecimalFormat; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -13,11 +14,13 @@ import java.util.Set; import info.openrocket.core.document.Simulation; import info.openrocket.core.logging.SimulationAbort; +import info.openrocket.core.logging.Warning; import info.openrocket.core.simulation.FlightDataBranch; import info.openrocket.core.simulation.FlightDataType; import info.openrocket.core.simulation.FlightEvent; import info.openrocket.core.preferences.ApplicationPreferences; import info.openrocket.core.util.LinearInterpolator; +import info.openrocket.swing.utils.DecimalFormatter; import org.jfree.chart.annotations.XYImageAnnotation; import org.jfree.chart.annotations.XYTitleAnnotation; @@ -31,6 +34,8 @@ import org.jfree.chart.ui.VerticalAlignment; import org.jfree.chart.ui.RectangleAnchor; import org.jfree.chart.ui.RectangleEdge; import org.jfree.chart.ui.RectangleInsets; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; @SuppressWarnings("serial") public class SimulationPlot extends Plot { @@ -106,32 +111,30 @@ public class SimulationPlot extends Plot eventTimes = new ArrayList<>(); - List eventLabels = new ArrayList<>(); List eventColors = new ArrayList<>(); List eventImages = new ArrayList<>(); + List> eventSets = new ArrayList<>(); // Plot the markers if (config.getDomainAxisType() == FlightDataType.TYPE_TIME && !preferences.getBoolean(ApplicationPreferences.MARKER_STYLE_ICON, false)) { - fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages); - plotVerticalLineMarkers(plot, eventTimes, eventLabels, eventColors); + fillEventLists(branch, eventColors, eventImages, eventSets); + plotVerticalLineMarkers(plot, eventColors, eventSets); } else { // Other domains are plotted as image annotations if (branch == -1) { // For icon markers, we need to do the plotting separately, otherwise you can have icon markers from e.g. // branch 1 be plotted on branch 0 for (int b = 0; b < simulation.getSimulatedData().getBranchCount(); b++) { - fillEventLists(b, eventTimes, eventLabels, eventColors, eventImages); + fillEventLists(b, eventColors, eventImages, eventSets); dataBranch = simulation.getSimulatedData().getBranch(b); - plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages); - eventTimes.clear(); - eventLabels.clear(); + plotIconMarkers(plot, simulation, b, eventImages, eventSets); + eventSets = new ArrayList<>(); eventColors.clear(); eventImages.clear(); } } else { - fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages); - plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages); + fillEventLists(branch, eventColors, eventImages, eventSets); + plotIconMarkers(plot, simulation, branch, eventImages, eventSets); } } } @@ -160,72 +163,86 @@ public class SimulationPlot extends Plot eventTimes, List eventLabels, - List eventColors, List eventImages) { + private void fillEventLists(int branch, + List eventColors, List eventImages, List> eventSets) { + Set eventSet = new HashSet<>(); Set typeSet = new HashSet<>(); double prevTime = -100; - String text = null; Color color = null; Image image = null; int maxOrdinal = -1; + for (EventDisplayInfo info : eventList) { if (branch >= 0 && branch != info.stage) { continue; } double t = info.time; - FlightEvent.Type type = info.event.getType(); + FlightEvent event = info.event; + FlightEvent.Type type = event.getType(); + if (Math.abs(t - prevTime) <= 0.05) { if (!typeSet.contains(type)) { - text = text + ", " + type.toString(); if (type.ordinal() > maxOrdinal) { - color = EventGraphics.getEventColor(info.event); - image = EventGraphics.getEventImage(info.event); + color = EventGraphics.getEventColor(event); + image = EventGraphics.getEventImage(event); maxOrdinal = type.ordinal(); } typeSet.add(type); + eventSet.add(event); } } else { - if (text != null) { - eventTimes.add(prevTime); - eventLabels.add(text); + if (!eventSet.isEmpty()) { eventColors.add(color); eventImages.add(image); + eventSets.add(eventSet); } prevTime = t; - text = type.toString(); - color = EventGraphics.getEventColor(info.event); - image = EventGraphics.getEventImage(info.event); + color = EventGraphics.getEventColor(event); + image = EventGraphics.getEventImage(event); typeSet.clear(); typeSet.add(type); + eventSet = new HashSet<>(); + eventSet.add(event); maxOrdinal = type.ordinal(); } - } - if (text != null) { - eventTimes.add(prevTime); - eventLabels.add(text); + if (!eventSet.isEmpty()) { eventColors.add(color); eventImages.add(image); + eventSets.add(eventSet); } } - private static void plotVerticalLineMarkers(XYPlot plot, List eventTimes, List eventLabels, List eventColors) { + private static String constructEventLabels(Set events) { + String text = ""; + + for (FlightEvent event : events) { + if (text != "") { + text += ", "; + } + text += event.getType().toString(); + } + + return text; + } + + private static void plotVerticalLineMarkers(XYPlot plot, List eventColors, List> eventSets) { double markerWidth = 0.01 * plot.getDomainAxis().getUpperBound(); // Domain time is plotted as vertical lines - for (int i = 0; i < eventTimes.size(); i++) { - double t = eventTimes.get(i); - String event = eventLabels.get(i); + for (int i = 0; i < eventSets.size(); i++) { + Set events = eventSets.get(i); + double t = ((FlightEvent)events.toArray()[0]).getTime(); + String eventLabel = constructEventLabels(events); Color color = eventColors.get(i); ValueMarker m = new ValueMarker(t); - m.setLabel(event); + m.setLabel(eventLabel); m.setPaint(color); m.setLabelPaint(color); m.setAlpha(0.7f); @@ -238,58 +255,133 @@ public class SimulationPlot extends Plot eventTimes, - List eventLabels, List eventImages) { + private void plotIconMarkers(XYPlot plot, Simulation simulation, int branch, List eventImages, List> eventSets) { + + FlightDataBranch dataBranch = simulation.getSimulatedData().getBranch(branch); + List time = dataBranch.get(FlightDataType.TYPE_TIME); List domain = dataBranch.get(config.getDomainAxisType()); - LinearInterpolator domainInterpolator = new LinearInterpolator(time, domain); + String xName = config.getDomainAxisType().getName(); - for (int i = 0; i < eventTimes.size(); i++) { - double t = eventTimes.get(i); - Image image = eventImages.get(i); + List minMaxAxes = filledConfig.getAllAxes(); - if (image == null) { - continue; - } + for (int axisno = 0; axisno < data.length; axisno++) { + // Image annotations are drawn using the data space defined by the left axis, so + // the position of annotations on the right axis need to be mapped to the left axis + // dataspace. + + double minLeft = minMaxAxes.get(0).getMinValue(); + double maxLeft = minMaxAxes.get(0).getMaxValue(); - double xcoord = domainInterpolator.getValue(t); + double minThis = minMaxAxes.get(axisno).getMinValue(); + double maxThis = minMaxAxes.get(axisno).getMaxValue(); - for (int index = 0; index < config.getDataCount(); index++) { - FlightDataType type = config.getType(index); - List range = dataBranch.get(type); + double slope = (maxLeft - minLeft)/(maxThis - minThis); + double intercept = (maxThis * minLeft - maxLeft * minThis)/(maxThis - minThis); + + XYSeriesCollection collection = data[axisno]; + for (XYSeries series : (List)(collection.getSeries())) { + Plot.MetadataXYSeries metaSeries = (Plot.MetadataXYSeries) series; - LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range); - // Image annotations are not supported on the right-side axis - // TODO: LOW: Can this be achieved by JFreeChart? - if (filledConfig.getAxis(index) != Util.PlotAxisSelection.LEFT.getValue()) { + if (metaSeries.getBranchIdx() != branch) { continue; } - double ycoord = rangeInterpolator.getValue(t); - if (!Double.isNaN(ycoord)) { - // Convert units + int dataTypeIdx = metaSeries.getDataIdx(); + FlightDataType type = config.getType(dataTypeIdx); + String yName = type.toString(); + List range = dataBranch.get(type); + LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range); + + for (int i = 0; i < eventSets.size(); i++) { + Set events = eventSets.get(i); + double t = ((FlightEvent)events.toArray()[0]).getTime(); + Image image = eventImages.get(i); + if (image == null) { + continue; + } + + double xcoord = domainInterpolator.getValue(t); + double ycoord = rangeInterpolator.getValue(t); + xcoord = config.getDomainAxisUnit().toUnit(xcoord); - ycoord = config.getUnit(index).toUnit(ycoord); - - // Get the sample index of the flight event. Because this can be an interpolation between two samples, - // take the closest sample. - final int sampleIdx; - Optional closestSample = time.stream() - .min(Comparator.comparingDouble(sample -> Math.abs(sample - t))); - sampleIdx = closestSample.map(time::indexOf).orElse(-1); - - String tooltipText = formatSampleTooltip(eventLabels.get(i), xcoord, config.getDomainAxisUnit().getUnit(), sampleIdx) ; - - XYImageAnnotation annotation = - new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER); - annotation.setToolTipText(tooltipText); - plot.addAnnotation(annotation); + ycoord = config.getUnit(dataTypeIdx).toUnit(ycoord); + + if (!Double.isNaN(ycoord)) { + // Get the sample index of the flight event. Because this can be an interpolation between two samples, + // take the closest sample. + final int sampleIdx; + Optional closestSample = time.stream() + .min(Comparator.comparingDouble(sample -> Math.abs(sample - t))); + sampleIdx = closestSample.map(time::indexOf).orElse(-1); + + // Convert units + String unitY = metaSeries.getUnit(); + String unitX = config.getDomainAxisUnit().getUnit(); + + + String tooltipText = formatEventTooltip(getNameBasedOnIdxAndSeries(metaSeries, sampleIdx), events, + xName, xcoord, unitX, + yName, ycoord, unitY, + sampleIdx) ; + double yloc = slope * ycoord + intercept; + XYImageAnnotation annotation = + new XYImageAnnotation(xcoord, yloc, image, RectangleAnchor.CENTER); + annotation.setToolTipText(tooltipText); + plot.addAnnotation(annotation); + } } } } } + protected String formatEventTooltip(String dataName, Set events, String xName, double dataX, String unitX, String yName, double dataY, String unitY, int sampleIdx) { + String ord_end = getOrdinalEnding(sampleIdx); + + DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); + DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); + + StringBuilder sb = new StringBuilder(); + + // start tooltip + sb.append(""); + + // Branchname(s) + sb.append(String.format("%s
", dataName)); + + // Any events? + if ((null != events) && (events.size() != 0)) { + // Pass through and collect any warnings + for (FlightEvent event : events) { + if (event.getType() == FlightEvent.Type.SIM_WARN) { + sb.append("Warning: " + ((Warning) event.getData()).toString() + "
"); + } + } + + // Now pass through and collect the other events + String eventStr = ""; + for (FlightEvent event : events) { + if (event.getType() != FlightEvent.Type.SIM_WARN) { + if (eventStr != "") { + eventStr = eventStr + ", "; + } + eventStr = eventStr + event.getType(); + } + } + sb.append(eventStr + "
"); + } + + sb.append(String.format("%s: %s %s
", xName, df_x.format(dataX), unitX)); + sb.append(String.format("%s: %s %s
", yName, df_y.format(dataY), unitY)); + sb.append(String.format("%d%s sample", sampleIdx, ord_end)); + + // End tooltip + sb.append(""); + + return sb.toString(); + } + private List buildEventInfo() { ArrayList eventList = new ArrayList<>(); From 43bfc2c3fc57943640b470991c0f5cd96b1288e7 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Mon, 23 Sep 2024 06:55:52 -0600 Subject: [PATCH 10/20] update tests for SIM_WARN events --- .../core/simulation/FlightEventsTest.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java b/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java index b04f221f7..394241c68 100644 --- a/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java +++ b/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java @@ -1,6 +1,12 @@ package info.openrocket.core.simulation; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + import info.openrocket.core.document.Simulation; +import info.openrocket.core.logging.Warning; import info.openrocket.core.rocketcomponent.AxialStage; import info.openrocket.core.rocketcomponent.BodyTube; import info.openrocket.core.rocketcomponent.FlightConfigurationId; @@ -11,15 +17,12 @@ import info.openrocket.core.rocketcomponent.Rocket; import info.openrocket.core.simulation.exception.SimulationException; import info.openrocket.core.util.BaseTestCase; import info.openrocket.core.util.TestRockets; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame;; /** * Tests to verify that simulations contain all the expected flight events. */ public class FlightEventsTest extends BaseTestCase { + private static final double EPSILON = 0.005; /** @@ -50,6 +53,7 @@ public class FlightEventsTest extends BaseTestCase { new FlightEvent(FlightEvent.Type.LAUNCHROD, 0.13, null), new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, motorMountTube), new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, stage), + new FlightEvent(FlightEvent.Type.SIM_WARN, 2.0, null, new Warning.HighSpeedDeployment(80.6)), new FlightEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, 2.001, parachute), new FlightEvent(FlightEvent.Type.APOGEE, 2.48, rocket), new FlightEvent(FlightEvent.Type.GROUND_HIT, 42.97, null), @@ -98,6 +102,9 @@ public class FlightEventsTest extends BaseTestCase { final ParallelStage sideBoosters = (ParallelStage) centerBoosterBody.getChild(1); final BodyTube sideBoosterBodies = (BodyTube) sideBoosters.getChild(1); + Warning warn = Warning.OPEN_AIRFRAME_FORWARD; + warn.setSources(new BodyTube[]{centerBoosterBody}); + // events whose time is too variable to check are given a time of 1200 for (int b = 0; b < 2; b++) { FlightEvent[] expectedEvents = switch (b) { @@ -129,6 +136,7 @@ public class FlightEventsTest extends BaseTestCase { new FlightEvent(FlightEvent.Type.BURNOUT, 2.01, centerBoosterBody), new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.01, centerBooster), new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.01, centerBooster), + new FlightEvent(FlightEvent.Type.SIM_WARN, 2.01, null, warn), new FlightEvent(FlightEvent.Type.TUMBLE, 2.85, null), new FlightEvent(FlightEvent.Type.APOGEE, 3.78, rocket), new FlightEvent(FlightEvent.Type.GROUND_HIT, 9.0, null), @@ -157,12 +165,9 @@ public class FlightEventsTest extends BaseTestCase { FlightEvent[] actualEvents = sim.getSimulatedData().getBranch(branchNo).getEvents().toArray(new FlightEvent[0]); - // Test event count - assertEquals(expectedEvents.length, actualEvents.length, "Branch " + branchNo + " invalid number of events "); - // Test that all expected events are present, in the right order, at the right // time, from the right sources - for (int i = 0; i < actualEvents.length; i++) { + for (int i = 0; i < Math.min(expectedEvents.length, actualEvents.length); i++) { final FlightEvent expected = expectedEvents[i]; final FlightEvent actual = actualEvents[i]; assertSame(expected.getType(), actual.getType(), @@ -184,6 +189,16 @@ public class FlightEventsTest extends BaseTestCase { // Test that the event sources are correct assertEquals(expected.getSource(), actual.getSource(), "Branch " + branchNo + " FlightEvent " + i + " type " + expected.getType() + " has wrong source "); + + // If it's a warning event, make sure the warning types match + if (expected.getType() == FlightEvent.Type.SIM_WARN) { + assertTrue(actual.getData() instanceof Warning, "SIM_WARN event data is not a Warning"); + assertTrue(((Warning) expected.getData()).equals(actual.getData()), "Expected: " + expected.getData() + " but was: " + actual.getData()); + } } + + // Test event count + assertEquals(expectedEvents.length, actualEvents.length, "Branch " + branchNo + " incorrect number of events "); + } } From 67efd148e539a14373f94f4f0985e8b9d85fcbc4 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Tue, 24 Sep 2024 11:39:49 -0600 Subject: [PATCH 11/20] Always show time; if there's another domain display it too --- .../swing/gui/plot/SimulationPlot.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 041278370..e157bd9e0 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -260,6 +260,7 @@ public class SimulationPlot extends Plot time = dataBranch.get(FlightDataType.TYPE_TIME); + String tName = FlightDataType.TYPE_TIME.getName(); List domain = dataBranch.get(config.getDomainAxisType()); LinearInterpolator domainInterpolator = new LinearInterpolator(time, domain); String xName = config.getDomainAxisType().getName(); @@ -317,11 +318,11 @@ public class SimulationPlot extends Plot events, String xName, double dataX, String unitX, String yName, double dataY, String unitY, int sampleIdx) { + protected String formatEventTooltip(String dataName, Set events, + String tName, double time, String unitT, + String xName, double dataX, String unitX, + String yName, double dataY, String unitY, int sampleIdx) { String ord_end = getOrdinalEnding(sampleIdx); + DecimalFormat df_t = DecimalFormatter.df(time, 2, false); DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); @@ -371,7 +376,10 @@ public class SimulationPlot extends Plot"); } - + + if (!tName.equals(xName)) { + sb.append(String.format("%s: %s %s
", tName, df_t.format(time), unitT)); + } sb.append(String.format("%s: %s %s
", xName, df_x.format(dataX), unitX)); sb.append(String.format("%s: %s %s
", yName, df_y.format(dataY), unitY)); sb.append(String.format("%d%s sample", sampleIdx, ord_end)); From c5fc251c02e44105925579fd8bb4fd4394d9531c Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Tue, 24 Sep 2024 12:08:21 -0600 Subject: [PATCH 12/20] don't try to add event icons if x or y is NaN noticed a bug that's been around since 23.09 -- if trying to plot using an x axis that includes NaN's when there is a flight event, we get an exception trying to position the icon. This checks for NaN's and doesn't plot in that case. --- .../openrocket/swing/gui/plot/SimulationPlot.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index e157bd9e0..86a7a880f 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -327,10 +327,13 @@ public class SimulationPlot extends Plot Date: Tue, 24 Sep 2024 17:49:44 -0600 Subject: [PATCH 13/20] Include warning text in SIM_WARN comments in CSV export --- core/src/main/java/info/openrocket/core/file/CSVExport.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/info/openrocket/core/file/CSVExport.java b/core/src/main/java/info/openrocket/core/file/CSVExport.java index 6fd26e23c..39bade149 100644 --- a/core/src/main/java/info/openrocket/core/file/CSVExport.java +++ b/core/src/main/java/info/openrocket/core/file/CSVExport.java @@ -259,8 +259,12 @@ public class CSVExport { private static void printEvent(PrintWriter writer, FlightEvent e, String commentStarter) { - writer.println(prependComment(commentStarter, "Event " + e.getType().name() + + writer.print(prependComment(commentStarter, "Event " + e.getType().name() + " occurred at t=" + TextUtil.doubleToString(e.getTime()) + " seconds")); + if (e.getType() == FlightEvent.Type.SIM_WARN) { + writer.print(": " + (Warning) e.getData()); + } + writer.println(); } private static void writeSimulationComments(PrintWriter writer, From a1eb7fa0c459dcd5bbf57240002181ec6dc2829d Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Wed, 25 Sep 2024 06:05:50 -0600 Subject: [PATCH 14/20] Remove sample index number from tooltips --- .../info/openrocket/swing/gui/plot/Plot.java | 26 +++++-------------- .../swing/gui/plot/SimulationPlot.java | 7 ++--- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index d6730b401..1e1e47ef7 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -211,7 +211,7 @@ public abstract class Plot, C extend // Determine the appropriate name based on the time and series String name = getNameBasedOnIdxAndSeries(ser, item); - return formatSampleTooltip(name, dataX, unitX, dataY, unitY, item); + return formatSampleTooltip(name, dataX, unitX, dataY, unitY); } }; @@ -298,8 +298,7 @@ public abstract class Plot, C extend } protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY, - int sampleIdx, boolean addYValue) { - String ord_end = getOrdinalEnding(sampleIdx); + boolean addYValue) { DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); @@ -313,28 +312,17 @@ public abstract class Plot, C extend } sb.append(String.format("X: %s %s
" + - "%d%s sample" + - "", df_x.format(dataX), unitX, sampleIdx, ord_end)); + "", df_x.format(dataX), unitX)); return sb.toString(); } - protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY, int sampleIdx) { - return formatSampleTooltip(dataName, dataX, unitX, dataY, unitY, sampleIdx, true); + protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY) { + return formatSampleTooltip(dataName, dataX, unitX, dataY, unitY, true); } - protected String formatSampleTooltip(String dataName, double dataX, String unitX, int sampleIdx) { - return formatSampleTooltip(dataName, dataX, unitX, 0, "", sampleIdx, false); - } - - protected String getOrdinalEnding(int n) { - if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) return "th"; - return switch (n % 10) { - case 1 -> "st"; - case 2 -> "nd"; - case 3 -> "rd"; - default -> "th"; - }; + protected String formatSampleTooltip(String dataName, double dataX, String unitX) { + return formatSampleTooltip(dataName, dataX, unitX, 0, "", false); } protected static class LegendItems implements LegendItemSource { diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 86a7a880f..27fb0f7f5 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -324,8 +324,7 @@ public class SimulationPlot extends Plot events, String tName, double time, String unitT, String xName, double dataX, String unitX, - String yName, double dataY, String unitY, int sampleIdx) { - String ord_end = getOrdinalEnding(sampleIdx); + String yName, double dataY, String unitY) { DecimalFormat df_t = DecimalFormatter.df(time, 2, false); DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); @@ -385,7 +383,6 @@ public class SimulationPlot extends Plot", xName, df_x.format(dataX), unitX)); sb.append(String.format("%s: %s %s
", yName, df_y.format(dataY), unitY)); - sb.append(String.format("%d%s sample", sampleIdx, ord_end)); // End tooltip sb.append(""); From 37c1bcb59024d88bef21f5d33493ee4751591f0b Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Fri, 27 Sep 2024 10:19:38 -0600 Subject: [PATCH 15/20] Put data names in sample tooltips instead of x and y --- .../gui/dialogs/componentanalysis/CAPlot.java | 6 +-- .../info/openrocket/swing/gui/plot/Plot.java | 53 +++++++++++-------- .../swing/gui/plot/SimulationPlot.java | 11 ++-- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java index ba98485dd..5ae60db0b 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java @@ -26,7 +26,7 @@ public class CAPlot extends Plot // Create the series for each component List allSeries = new ArrayList<>(); for (int i = 0; i < components.size(); i++) { - XYSeries series = createSingleSeries(startIndex*1000 + i, type, unit, branch, branchIdx, branchName, baseName, + XYSeries series = createSingleSeries(startIndex*1000 + i, type, unit, branch, branchIdx, branchName, dataIndex, baseName, components.get(i), componentNames.get(i)); allSeries.add(series); } @@ -35,10 +35,10 @@ public class CAPlot extends Plot } private XYSeries createSingleSeries(int key, CADataType type, Unit unit, - CADataBranch branch, int branchIdx, String branchName, String baseName, + CADataBranch branch, int branchIdx, String branchName, int dataIndex, String baseName, RocketComponent component, String componentName) { // Default implementation for regular DataBranch - MetadataXYSeries series = new MetadataXYSeries(key, false, true, branchIdx, unit.getUnit(), + MetadataXYSeries series = new MetadataXYSeries(key, false, true, branchIdx, dataIndex, unit.getUnit(), branchName, baseName); // Create a new description that includes the component name diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index 1e1e47ef7..f30be8afd 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -202,16 +202,24 @@ public abstract class Plot, C extend return null; } MetadataXYSeries ser = (MetadataXYSeries) collection.getSeries(series); - String unitY = ser.getUnit(); - String unitX = domainUnit.getUnit(); - - double dataY = dataset.getYValue(series, item); - double dataX = dataset.getXValue(series, item); // Determine the appropriate name based on the time and series String name = getNameBasedOnIdxAndSeries(ser, item); - return formatSampleTooltip(name, dataX, unitX, dataY, unitY); + int dataTypeIdx = ser.getDataIdx(); + DataType type = config.getType(dataTypeIdx); + + String nameX = config.getDomainAxisType().getName(); + String unitX = domainUnit.getUnit(); + double dataX = dataset.getXValue(series, item); + + String nameY = type.toString(); + double dataY = dataset.getYValue(series, item); + String unitY = ser.getUnit(); + + return formatSampleTooltip(name, + nameX, dataX, unitX, + nameY, dataY, unitY); } }; @@ -297,32 +305,33 @@ public abstract class Plot, C extend return type; } - protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY, - boolean addYValue) { + protected String formatSampleTooltip(String dataName, + String nameX, double dataX, String unitX, + String nameY, double dataY, String unitY) { - DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); + final String strFormat = "%s: %s %s
"; + DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); StringBuilder sb = new StringBuilder(); - sb.append(String.format("" + - "%s
", dataName)); + sb.append(""); - if (addYValue) { - sb.append(String.format("Y: %s %s
", df_y.format(dataY), unitY)); + sb.append(String.format("%s
", dataName)); + + sb.append(String.format(strFormat, nameX, df_x.format(dataX), unitX)); + + if (!Double.isNaN(dataY)) { + DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); + sb.append(String.format(strFormat, nameY, df_y.format(dataY), unitY)); } - sb.append(String.format("X: %s %s
" + - "", df_x.format(dataX), unitX)); - + sb.append(""); + return sb.toString(); } - protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY) { - return formatSampleTooltip(dataName, dataX, unitX, dataY, unitY, true); - } - - protected String formatSampleTooltip(String dataName, double dataX, String unitX) { - return formatSampleTooltip(dataName, dataX, unitX, 0, "", false); + protected String formatSampleTooltip(String dataName, String nameX, double dataX, String unitX) { + return formatSampleTooltip(dataName, nameX, dataX, unitX, "", Double.NaN, ""); } protected static class LegendItems implements LegendItemSource { diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 27fb0f7f5..1519f3d9a 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -282,14 +282,13 @@ public class SimulationPlot extends Plot)(collection.getSeries())) { - Plot.MetadataXYSeries metaSeries = (Plot.MetadataXYSeries) series; + for (MetadataXYSeries series : (List)(collection.getSeries())) { - if (metaSeries.getBranchIdx() != branch) { + if (series.getBranchIdx() != branch) { continue; } - int dataTypeIdx = metaSeries.getDataIdx(); + int dataTypeIdx = series.getDataIdx(); FlightDataType type = config.getType(dataTypeIdx); String yName = type.toString(); List range = dataBranch.get(type); @@ -319,9 +318,9 @@ public class SimulationPlot extends Plot Date: Fri, 27 Sep 2024 11:00:43 -0600 Subject: [PATCH 16/20] Add time to sample tooltip when available --- .../info/openrocket/swing/gui/plot/Plot.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index f30be8afd..e3cbe5374 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -3,6 +3,7 @@ package info.openrocket.swing.gui.plot; import info.openrocket.core.l10n.Translator; import info.openrocket.core.simulation.DataBranch; import info.openrocket.core.simulation.DataType; +import info.openrocket.core.simulation.FlightDataType; import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; import info.openrocket.core.unit.UnitGroup; @@ -197,6 +198,7 @@ public abstract class Plot, C extend StandardXYToolTipGenerator tooltipGenerator = new StandardXYToolTipGenerator() { @Override public String generateToolTip(XYDataset dataset, int series, int item) { + XYSeriesCollection collection = data[finalAxisno]; if (collection.getSeriesCount() == 0) { return null; @@ -208,16 +210,25 @@ public abstract class Plot, C extend int dataTypeIdx = ser.getDataIdx(); DataType type = config.getType(dataTypeIdx); - + + String nameT = FlightDataType.TYPE_TIME.getName(); + double dataT = Double.NaN; + List time = allBranches.get(ser.getBranchIdx()).get((T)FlightDataType.TYPE_TIME); + if (null != time) { + dataT = time.get(item); + } + String unitT = FlightDataType.TYPE_TIME.getUnitGroup().getDefaultUnit().toString(); + String nameX = config.getDomainAxisType().getName(); - String unitX = domainUnit.getUnit(); double dataX = dataset.getXValue(series, item); + String unitX = domainUnit.getUnit(); String nameY = type.toString(); double dataY = dataset.getYValue(series, item); String unitY = ser.getUnit(); return formatSampleTooltip(name, + nameT, dataT, unitT, nameX, dataX, unitX, nameY, dataY, unitY); } @@ -306,6 +317,7 @@ public abstract class Plot, C extend } protected String formatSampleTooltip(String dataName, + String nameT, double dataT, String unitT, String nameX, double dataX, String unitX, String nameY, double dataY, String unitY) { @@ -318,20 +330,28 @@ public abstract class Plot, C extend sb.append(String.format("%s
", dataName)); - sb.append(String.format(strFormat, nameX, df_x.format(dataX), unitX)); - + // If I was given valid Y data, put it in tooltip if (!Double.isNaN(dataY)) { DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); sb.append(String.format(strFormat, nameY, df_y.format(dataY), unitY)); } + // Assuming X data is valid + sb.append(String.format(strFormat, nameX, df_x.format(dataX), unitX)); + + // If I've got time data, and my domain isn't time, add time to tooltip + if (!Double.isNaN(dataT) && !nameX.equals(nameT)) { + DecimalFormat df_t = DecimalFormatter.df(dataT, 2, false); + sb.append(String.format(strFormat, nameT, df_t.format(dataT), unitT)); + } + sb.append(""); return sb.toString(); } protected String formatSampleTooltip(String dataName, String nameX, double dataX, String unitX) { - return formatSampleTooltip(dataName, nameX, dataX, unitX, "", Double.NaN, ""); + return formatSampleTooltip(dataName, "", Double.NaN, "", nameX, dataX, unitX, "", Double.NaN, ""); } protected static class LegendItems implements LegendItemSource { From bc8c0b48be1d54991cec63af6919d0d1768b9c30 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Fri, 27 Sep 2024 13:40:42 -0600 Subject: [PATCH 17/20] Merge formatSampleTooltip and formatEventTooltip into a single formatTooltip to ensure consistency in formatting --- .../info/openrocket/swing/gui/plot/Plot.java | 49 +++++++++++---- .../swing/gui/plot/SimulationPlot.java | 60 ++----------------- 2 files changed, 43 insertions(+), 66 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index e3cbe5374..ef62eb379 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -1,9 +1,11 @@ package info.openrocket.swing.gui.plot; import info.openrocket.core.l10n.Translator; +import info.openrocket.core.logging.Warning; import info.openrocket.core.simulation.DataBranch; import info.openrocket.core.simulation.DataType; import info.openrocket.core.simulation.FlightDataType; +import info.openrocket.core.simulation.FlightEvent; import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; import info.openrocket.core.unit.UnitGroup; @@ -53,6 +55,7 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; /* * TODO: It should be possible to simplify this code quite a bit by using a single Renderer instance for @@ -227,10 +230,11 @@ public abstract class Plot, C extend double dataY = dataset.getYValue(series, item); String unitY = ser.getUnit(); - return formatSampleTooltip(name, - nameT, dataT, unitT, - nameX, dataX, unitX, - nameY, dataY, unitY); + return formatTooltip(name, + nameT, dataT, unitT, + nameX, dataX, unitX, + nameY, dataY, unitY, + null); } }; @@ -316,10 +320,11 @@ public abstract class Plot, C extend return type; } - protected String formatSampleTooltip(String dataName, - String nameT, double dataT, String unitT, - String nameX, double dataX, String unitX, - String nameY, double dataY, String unitY) { + protected String formatTooltip(String dataName, + String nameT, double dataT, String unitT, + String nameX, double dataX, String unitX, + String nameY, double dataY, String unitY, + Set events) { final String strFormat = "%s: %s %s
"; @@ -330,7 +335,29 @@ public abstract class Plot, C extend sb.append(String.format("%s
", dataName)); - // If I was given valid Y data, put it in tooltip + // Any events? + if ((null != events) && (events.size() != 0)) { + // Pass through and collect any warnings + for (FlightEvent event : events) { + if (event.getType() == FlightEvent.Type.SIM_WARN) { + sb.append("Warning: " + ((Warning) event.getData()).toString() + "
"); + } + } + + // Now pass through and collect the other events + String eventStr = ""; + for (FlightEvent event : events) { + if (event.getType() != FlightEvent.Type.SIM_WARN) { + if (eventStr != "") { + eventStr = eventStr + ", "; + } + eventStr = eventStr + event.getType(); + } + } + sb.append(eventStr + "
"); + } + + // Valid Y data? if (!Double.isNaN(dataY)) { DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); sb.append(String.format(strFormat, nameY, df_y.format(dataY), unitY)); @@ -350,8 +377,8 @@ public abstract class Plot, C extend return sb.toString(); } - protected String formatSampleTooltip(String dataName, String nameX, double dataX, String unitX) { - return formatSampleTooltip(dataName, "", Double.NaN, "", nameX, dataX, unitX, "", Double.NaN, ""); + protected String formatTooltip(String dataName, String nameX, double dataX, String unitX) { + return formatTooltip(dataName, "", Double.NaN, "", nameX, dataX, unitX, "", Double.NaN, "", null); } protected static class LegendItems implements LegendItemSource { diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 1519f3d9a..93f5fe269 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -320,10 +320,11 @@ public class SimulationPlot extends Plot events, - String tName, double time, String unitT, - String xName, double dataX, String unitX, - String yName, double dataY, String unitY) { - - DecimalFormat df_t = DecimalFormatter.df(time, 2, false); - DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); - DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); - - StringBuilder sb = new StringBuilder(); - - // start tooltip - sb.append(""); - - // Branchname(s) - sb.append(String.format("%s
", dataName)); - - // Any events? - if ((null != events) && (events.size() != 0)) { - // Pass through and collect any warnings - for (FlightEvent event : events) { - if (event.getType() == FlightEvent.Type.SIM_WARN) { - sb.append("Warning: " + ((Warning) event.getData()).toString() + "
"); - } - } - - // Now pass through and collect the other events - String eventStr = ""; - for (FlightEvent event : events) { - if (event.getType() != FlightEvent.Type.SIM_WARN) { - if (eventStr != "") { - eventStr = eventStr + ", "; - } - eventStr = eventStr + event.getType(); - } - } - sb.append(eventStr + "
"); - } - - if (!tName.equals(xName)) { - sb.append(String.format("%s: %s %s
", tName, df_t.format(time), unitT)); - } - sb.append(String.format("%s: %s %s
", xName, df_x.format(dataX), unitX)); - sb.append(String.format("%s: %s %s
", yName, df_y.format(dataY), unitY)); - - // End tooltip - sb.append(""); - - return sb.toString(); - } - private List buildEventInfo() { ArrayList eventList = new ArrayList<>(); From ac8d182b9a7c2c43a00f55ca6f8e544d3dd90183 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Fri, 27 Sep 2024 13:55:12 -0600 Subject: [PATCH 18/20] update fileformat.txt --- fileformat.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fileformat.txt b/fileformat.txt index 144751d0b..f6503c444 100644 --- a/fileformat.txt +++ b/fileformat.txt @@ -71,3 +71,5 @@ The following file format versions exist: Added a priority attribute to simulation warnings. Added document preferences (). Added wind model settings (), and windmodeltype to simulation conditions. + Added warning flight events + From c462122a457781590710a28061fadcf79debacf9 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Sat, 28 Sep 2024 06:20:52 -0600 Subject: [PATCH 19/20] update axis min and max to reflect all branches, not just first branch --- .../src/main/java/info/openrocket/swing/gui/plot/Plot.java | 7 +++---- .../info/openrocket/swing/gui/plot/SimulationPlot.java | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index ef62eb379..7c24334aa 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java @@ -73,7 +73,7 @@ public abstract class Plot, C extend protected final List renderers = new ArrayList<>(); protected final LegendItems legendItems; protected final XYSeriesCollection[] data; - protected final C filledConfig; // Configuration after using 'fillAutoAxes' + protected final C filledConfig; // Configuration after using 'fillAutoAxes' and 'fitAxes' protected final JFreeChart chart; @@ -124,9 +124,8 @@ public abstract class Plot, C extend int seriesCount = 0; // Compute the axes based on the min and max value of all branches - C plotConfig = filledConfig.cloneConfiguration(); - plotConfig.fitAxes(allBranches); - List minMaxAxes = plotConfig.getAllAxes(); + filledConfig.fitAxes(allBranches); + List minMaxAxes = filledConfig.getAllAxes(); // Create the XYSeries objects from the flight data and store into the collections String[] axisLabel = new String[2]; diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 93f5fe269..687d710db 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -274,10 +274,10 @@ public class SimulationPlot extends Plot Date: Sat, 28 Sep 2024 13:00:01 -0600 Subject: [PATCH 20/20] Make sure lower branch numbered icons are on top of higher numbered, so tooltips show correct title --- .../info/openrocket/swing/gui/plot/SimulationPlot.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java index 687d710db..5a9a38031 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java @@ -123,8 +123,9 @@ public class SimulationPlot extends Plot= 0; b--) { fillEventLists(b, eventColors, eventImages, eventSets); dataBranch = simulation.getSimulatedData().getBranch(b); plotIconMarkers(plot, simulation, b, eventImages, eventSets); @@ -157,9 +158,9 @@ public class SimulationPlot extends Plot dataIdx) { + if (separationIdx != -1 && separationIdx >= dataIdx) { newBranchName.append(" + ").append(allBranches.get(i).getName()); } }