Merge pull request #2562 from JoePfeiffer/flight-warning-event
Flight warning event
This commit is contained in:
commit
9e83cbe696
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -80,4 +80,4 @@ public interface ElementHandler {
|
||||
public abstract void endHandler(String element, HashMap<String, String> attributes,
|
||||
String content, WarningSet warnings) throws SAXException;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 /////////////
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 = هبوط
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ");
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"));
|
||||
|
Loading…
x
Reference in New Issue
Block a user