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 c661ace65..a05125565 100644 --- a/core/src/main/java/info/openrocket/core/document/Simulation.java +++ b/core/src/main/java/info/openrocket/core/document/Simulation.java @@ -357,7 +357,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; } } 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, 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 d8f8d2285..1891a92a0 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 @@ -430,7 +430,24 @@ public class OpenRocketSaver extends RocketSaver { indent++; for (Warning w : data.getWarningSet()) { - writeElementWithAttribute("warning", "priority", w.getPriority().getExportLabel(), TextUtil.escapeXML(w.toString())); + writeln("<warning>"); + 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("</warning>"); } // Check whether to store data @@ -606,16 +623,21 @@ public class OpenRocketSaver extends RocketSaver { // Write events for (FlightEvent event : branch.getEvents()) { String eventStr = "<event time=\"" + TextUtil.doubleToString(event.getTime()) - + "\" type=\"" + enumToXMLName(event.getType()); + + "\" type=\"" + enumToXMLName(event.getType()) + "\""; + if (event.getSource() != null) { - eventStr += "\" source=\"" + TextUtil.escapeXML(event.getSource().getID()); + eventStr += " source=\"" + TextUtil.escapeXML(event.getSource().getID()) + "\""; } + if (event.getType() == FlightEvent.Type.SIM_WARN) { + eventStr += " id=\"" + TextUtil.escapeXML(((Warning) event.getData()).getID()) + "\""; + } + if (event.getType() == FlightEvent.Type.SIM_ABORT) { - eventStr += "\" cause=\"" + enumToXMLName(((SimulationAbort)(event.getData())).getCause()); + eventStr += " cause=\"" + enumToXMLName(((SimulationAbort)(event.getData())).getCause()) + "\""; } - eventStr += "\"/>"; + eventStr += "/>"; writeln(eventStr); } @@ -676,14 +698,6 @@ public class OpenRocketSaver extends RocketSaver { content = ""; writeln("<" + element + ">" + TextUtil.escapeXML(content) + "</" + element + ">"); } - - private void writeElementWithAttribute(String element, String attributeName, String attribute, Object content) throws IOException { - content = content == null ? "" : content; - - writeln("<" + element + " " + attributeName + " = \"" + attribute + "\">" + TextUtil.escapeXML(content) + "</" + element + ">"); - } - - 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<RocketComponent> 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<String, String> attributes, + WarningSet warnings) { + return PlainTextHandler.INSTANCE; + } + + @Override + public void closeElement(String element, HashMap<String, String> 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<String, String> 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<String, String> 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 5f97fa01c..e7b9b46a1 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,20 @@ public abstract class Message implements Cloneable { */ public abstract boolean replaceBy(Message other); + /** + * Return the ID + */ + 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. * @return the rocket component(s) that are the source of this warning. Returns null if no sources are specified. 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 <code>Message</code>s. 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<E extends Message> extends AbstractSet<E> 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<E extends Message> extends AbstractSet<E> imple } m.setSources(sources); return add(m); - } + } /** * Add a <code>Message</code> of the specified type with the specified discriminator to the @@ -149,6 +150,14 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> 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 ///////////// 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 0e68c559f..97dd2b334 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/FlightEvent.java b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java index 3e7f80b49..37b703f5a 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<FlightEvent> { */ 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 @@ -118,8 +124,8 @@ public class FlightEvent implements Comparable<FlightEvent> { 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) { @@ -177,6 +183,13 @@ public class FlightEvent implements Comparable<FlightEvent> { // 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() { @@ -241,6 +254,17 @@ public class FlightEvent implements Comparable<FlightEvent> { } } 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"); + } + 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/java/info/openrocket/core/simulation/RK4SimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java index f94746090..b37733e48 100644 --- a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java @@ -401,7 +401,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()) { @@ -421,7 +421,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 4768bc3d5..54c279bfc 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; @@ -418,6 +419,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 e328d2871..ffd6119af 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 @@ -637,7 +637,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); } } } diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 272459b40..091c418a1 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -2141,6 +2141,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 ca25e2b89..609c91090 100644 --- a/core/src/main/resources/l10n/messages_ar.properties +++ b/core/src/main/resources/l10n/messages_ar.properties @@ -1711,6 +1711,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 9499d643c..9544e29a4 100644 --- a/core/src/main/resources/l10n/messages_de.properties +++ b/core/src/main/resources/l10n/messages_de.properties @@ -1265,6 +1265,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stufentrennung FlightEvent.Type.APOGEE = Apog�um FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Ausl�sung des Bergungssystems FlightEvent.Type.GROUND_HIT = Landung +FlightEvent.Type.SIM_WARN = Warnung FlightEvent.Type.SIMULATION_END = Ende der Simulation FlightEvent.Type.ALTITUDE = H�hen�nderung diff --git a/core/src/main/resources/l10n/messages_es.properties b/core/src/main/resources/l10n/messages_es.properties index 61fedf408..154d5a816 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 9b3e50229..98ca53516 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 950a6e7ce..19f4f4715 100644 --- a/core/src/main/resources/l10n/messages_it.properties +++ b/core/src/main/resources/l10n/messages_it.properties @@ -1269,6 +1269,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 b1e5c1c7c..86e9a7789 100644 --- a/core/src/main/resources/l10n/messages_ja.properties +++ b/core/src/main/resources/l10n/messages_ja.properties @@ -1316,6 +1316,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 690af23a8..ecd1496c5 100644 --- a/core/src/main/resources/l10n/messages_nl.properties +++ b/core/src/main/resources/l10n/messages_nl.properties @@ -1634,6 +1634,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 4428b15fd..d46226ee5 100644 --- a/core/src/main/resources/l10n/messages_pl.properties +++ b/core/src/main/resources/l10n/messages_pl.properties @@ -1210,6 +1210,7 @@ ComponentInfo.EngineBlock = <b>Blokada silnika</b> 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 697c732d3..c71236ce9 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 6bd2670b3..03e9250f1 100644 --- a/core/src/main/resources/l10n/messages_ru.properties +++ b/core/src/main/resources/l10n/messages_ru.properties @@ -1683,6 +1683,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 e3a6b2714..c3e5770ec 100644 --- a/core/src/main/resources/l10n/messages_uk_UA.properties +++ b/core/src/main/resources/l10n/messages_uk_UA.properties @@ -2007,6 +2007,7 @@ FlightEvent.Type.SIMULATION_END = \u0417\u0430\u043a\u0456\u043d\u0447\u0435\u04 FlightEvent.Type.ALTITUDE = \u0417\u043c\u0456\u043d\u0430 \u0432\u0438\u0441\u043e\u0442\u0438 FlightEvent.Type.TUMBLE = \u041f\u0435\u0440\u0435\u043a\u0438\u0434\u0430\u043d\u043d\u044f FlightEvent.Type.EXCEPTION = \u0412\u0438\u043d\u044f\u0442\u043e\u043a +>>>>>>> unstable ! ThrustCurveMotorColumns TCurveMotorCol.MANUFACTURER = \u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a diff --git a/core/src/main/resources/l10n/messages_zh_CN.properties b/core/src/main/resources/l10n/messages_zh_CN.properties index 49efc0121..f9903be6f 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 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 fa6a893f3..3654f5eee 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), @@ -99,6 +103,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) { @@ -130,6 +137,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), @@ -158,12 +166,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(), @@ -185,6 +190,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 "); + } } 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 (<docprefs>). Added wind model settings (<wind mode="{average or multilevel}">), and windmodeltype to simulation conditions. + Added warning flight events + 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<CADataType, CADataBranch, CAPlotConfiguration> // Create the series for each component List<XYSeries> 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<CADataType, CADataBranch, CAPlotConfiguration> } 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/EventGraphics.java b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java index 06911c9f1..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 @@ -9,20 +9,27 @@ 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 { - 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; 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); @@ -41,29 +48,38 @@ 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)); } private static final Map<FlightEvent.Type, Image> 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<MessagePriority, Image> MESSAGE_IMAGES = new HashMap<MessagePriority, Image>(); + 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 <KeyType> void loadImage(Map<KeyType, Image> imageMap, KeyType type, String file) { InputStream is; is = ClassLoader.getSystemResourceAsStream(file); @@ -74,10 +90,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/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java index ce9c532c3..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 @@ -1,8 +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; @@ -52,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 @@ -69,7 +73,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend protected final List<ModifiedXYItemRenderer> 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; @@ -101,8 +105,8 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, 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); @@ -120,9 +124,8 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, 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<Axis> minMaxAxes = plotConfig.getAllAxes(); + filledConfig.fitAxes(allBranches); + List<Axis> minMaxAxes = filledConfig.getAllAxes(); // Create the XYSeries objects from the flight data and store into the collections String[] axisLabel = new String[2]; @@ -197,21 +200,40 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, 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; } 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, item); + int dataTypeIdx = ser.getDataIdx(); + DataType type = config.getType(dataTypeIdx); + + String nameT = FlightDataType.TYPE_TIME.getName(); + double dataT = Double.NaN; + List<Double> 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(); + 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 formatTooltip(name, + nameT, dataT, unitT, + nameX, dataX, unitX, + nameY, dataY, unitY, + null); } }; @@ -278,7 +300,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend protected List<XYSeries> 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<Double> plotx = branch.get(filledConfig.getDomainAxisType()); List<Double> ploty = branch.get(type); @@ -297,44 +319,65 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend return type; } - protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY, - int sampleIdx, boolean addYValue) { - String ord_end = getOrdinalEnding(sampleIdx); + protected String formatTooltip(String dataName, + String nameT, double dataT, String unitT, + String nameX, double dataX, String unitX, + String nameY, double dataY, String unitY, + Set<FlightEvent> events) { - DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false); + final String strFormat = "%s: %s %s<br>"; + DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false); StringBuilder sb = new StringBuilder(); - sb.append(String.format("<html>" + - "<b><i>%s</i></b><br>", dataName)); + sb.append("<html>"); - if (addYValue) { - sb.append(String.format("Y: %s %s<br>", df_y.format(dataY), unitY)); + sb.append(String.format("<b><i>%s</i></b><br>", 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("<b><i>Warning: " + ((Warning) event.getData()).toString() + "</b></i><br>"); + } + } + + // 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 + "<br>"); } - sb.append(String.format("X: %s %s<br>" + - "%d<sup>%s</sup> sample" + - "</html>", df_x.format(dataX), unitX, sampleIdx, ord_end)); + // 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)); + } + // 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("</html>"); + 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, int sampleIdx) { - return formatSampleTooltip(dataName, dataX, unitX, 0, "", sampleIdx, false); - } - - private 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 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 { @@ -540,20 +583,27 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, 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 +612,10 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, 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 40ff38540..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 @@ -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<FlightDataType, FlightDataBranch, SimulationPlotConfiguration> { @@ -106,32 +111,31 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul plot.clearAnnotations(); // Store flight event information - List<Double> eventTimes = new ArrayList<>(); - List<String> eventLabels = new ArrayList<>(); List<Color> eventColors = new ArrayList<>(); List<Image> eventImages = new ArrayList<>(); + List<Set<FlightEvent>> 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); + // branch 1 be plotted on branch 0. Also, need to take the branches in reverse order so lower number + // branches get their icons put on top of higher number and the tooltip headings are correct. + for (int b = simulation.getSimulatedData().getBranchCount() - 1; b >= 0; b--) { + 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); } } } @@ -154,78 +158,92 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul double separationTime = allBranches.get(i).getSeparationTime(); int separationIdx = allBranches.get(i).getDataIndexOfTime(separationTime); - // If the separation time is after the current data index, the stage is still attached, so add the + // If the separation time is at or after the current data index, the stage is still attached, so add the // stage name to the series name - if (separationIdx != -1 && separationIdx > dataIdx) { + if (separationIdx != -1 && separationIdx >= dataIdx) { newBranchName.append(" + ").append(allBranches.get(i).getName()); } } - return newBranchName + ": " + ser.getBaseName(); } - private void fillEventLists(int branch, List<Double> eventTimes, List<String> eventLabels, - List<Color> eventColors, List<Image> eventImages) { + private void fillEventLists(int branch, + List<Color> eventColors, List<Image> eventImages, List<Set<FlightEvent>> eventSets) { + Set<FlightEvent> eventSet = new HashSet<>(); Set<FlightEvent.Type> 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(type); - image = EventGraphics.getEventImage(type); + 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(type); - image = EventGraphics.getEventImage(type); + 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<Double> eventTimes, List<String> eventLabels, List<Color> eventColors) { + private static String constructEventLabels(Set<FlightEvent> events) { + String text = ""; + + for (FlightEvent event : events) { + if (text != "") { + text += ", "; + } + text += event.getType().toString(); + } + + return text; + } + + private static void plotVerticalLineMarkers(XYPlot plot, List<Color> eventColors, List<Set<FlightEvent>> 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<FlightEvent> 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,53 +256,85 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul } } - private void plotIconMarkers(XYPlot plot, FlightDataBranch dataBranch, List<Double> eventTimes, - List<String> eventLabels, List<Image> eventImages) { + private void plotIconMarkers(XYPlot plot, Simulation simulation, int branch, List<Image> eventImages, List<Set<FlightEvent>> eventSets) { + + FlightDataBranch dataBranch = simulation.getSimulatedData().getBranch(branch); + List<Double> time = dataBranch.get(FlightDataType.TYPE_TIME); + String tName = FlightDataType.TYPE_TIME.getName(); List<Double> 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<Axis> 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 minThis = minMaxAxes.get(axisno).getMinValue(); + double maxThis = minMaxAxes.get(axisno).getMaxValue(); + + double slope = (maxLeft - minLeft)/(maxThis - minThis); + double intercept = (maxThis * minLeft - maxLeft * minThis)/(maxThis - minThis); + + XYSeriesCollection collection = data[axisno]; + for (MetadataXYSeries series : (List<MetadataXYSeries>)(collection.getSeries())) { - double xcoord = domainInterpolator.getValue(t); - - for (int index = 0; index < config.getDataCount(); index++) { - FlightDataType type = config.getType(index); - List<Double> range = dataBranch.get(type); - - 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 (series.getBranchIdx() != branch) { continue; } - double ycoord = rangeInterpolator.getValue(t); - if (!Double.isNaN(ycoord)) { - // Convert units + int dataTypeIdx = series.getDataIdx(); + FlightDataType type = config.getType(dataTypeIdx); + String yName = type.toString(); + List<Double> range = dataBranch.get(type); + LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range); + + for (int i = 0; i < eventSets.size(); i++) { + Set<FlightEvent> 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<Double> 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<Double> closestSample = time.stream() + .min(Comparator.comparingDouble(sample -> Math.abs(sample - t))); + sampleIdx = closestSample.map(time::indexOf).orElse(-1); + + // Convert units + String unitX = config.getDomainAxisUnit().getUnit(); + String unitY = series.getUnit(); + String unitT = FlightDataType.TYPE_TIME.getUnitGroup().getDefaultUnit().toString(); + String tooltipText = formatTooltip(getNameBasedOnIdxAndSeries(series, sampleIdx), + tName, t, unitT, + xName, xcoord, unitX, + yName, ycoord, unitY, + events); + double yloc = slope * ycoord + intercept; + + if (!Double.isNaN(xcoord) && !Double.isNaN(ycoord)) { + XYImageAnnotation annotation = + new XYImageAnnotation(xcoord, yloc, image, RectangleAnchor.CENTER); + annotation.setToolTipText(tooltipText); + plot.addAnnotation(annotation); + } + } } } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java index 258ad08cf..cd6abbde2 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java @@ -32,6 +32,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -48,6 +49,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -62,6 +64,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -76,6 +79,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -91,6 +95,8 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); + config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -102,6 +108,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.addPlotDataType(FlightDataType.TYPE_BASE_DRAG_COEFF, 0); config.addPlotDataType(FlightDataType.TYPE_PRESSURE_DRAG_COEFF, 0); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -120,6 +127,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -136,6 +144,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); @@ -151,6 +160,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.TUMBLE, true); config.setEvent(FlightEvent.Type.EXCEPTION, true); + config.setEvent(FlightEvent.Type.SIM_WARN, true); config.setEvent(FlightEvent.Type.SIM_ABORT, true); configs.add(config); diff --git a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java index b1034e9c8..0950bfef9 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java @@ -174,8 +174,7 @@ public class SimulationPlotPanel extends PlotPanel<FlightDataType, FlightDataBra col0.setPreferredWidth(w); col0.setMaxWidth(w); table.addMouseListener(new GUIUtil.BooleanTableClickListener(table)); - selectorPanel.add(new JScrollPane(table), "height 200px, width 200lp, grow 1, wrap rel"); - + selectorPanel.add(new JScrollPane(table), "width 200lp, grow 1, wrap rel"); //// All + None buttons JButton button = new JButton(trans.get("simplotpanel.but.All"));