Merge branch 'unstable' into dark-theme
This commit is contained in:
commit
86a71d5375
@ -95,7 +95,10 @@ BasicFrame.WarningDialog.title = Warnings while opening file
|
||||
BasicFrame.WarningDialog.saving.title = Warnings while opening file
|
||||
BasicFrame.ErrorWarningDialog.txt1 = <html>Please <b>correct the errors</b>.</html>
|
||||
BasicFrame.ErrorWarningDialog.saving.title = Errors/Warnings while saving file
|
||||
BasicFrame.lbl.SaveRocketInfo = Save Design Info
|
||||
|
||||
! SaveDesignInfoPanel
|
||||
SaveDesignInfoPanel.lbl.FillInInfo = (Optional) Fill in the design information for this file
|
||||
|
||||
! General error messages used in multiple contexts
|
||||
error.fileExists.title = File exists
|
||||
@ -582,12 +585,13 @@ simpanel.but.ttip.editsim = Edit the selected simulation
|
||||
simpanel.but.ttip.runsimu = Re-run the selected simulations
|
||||
simpanel.but.ttip.deletesim = Delete the selected simulations
|
||||
simpanel.pop.edit = Edit
|
||||
simpanel.pop.copyValues = Copy values
|
||||
simpanel.pop.plot = Plot / Export
|
||||
simpanel.pop.run = Run
|
||||
simpanel.pop.delete = Delete
|
||||
simpanel.pop.duplicate = Duplicate
|
||||
simpanel.pop.exportSimTableToCSV = Export simulation table as CSV file
|
||||
simpanel.pop.exportSelectedSimsToCSV = Export simulations as CSV file
|
||||
simpanel.pop.exportSelectedSimsToCSV = Export simulation(s) as CSV file
|
||||
simpanel.pop.exportToCSV.save.dialog.title = Save as CSV file
|
||||
simpanel.dlg.no.simulation.table.rows = Simulation table has no entries\u2026 Please run a simulation first.
|
||||
simpanel.checkbox.donotask = Do not ask me again
|
||||
@ -595,6 +599,7 @@ simpanel.lbl.defpref = You can change the default operation in the preferences.
|
||||
simpanel.dlg.lbl.DeleteSim1 = Delete the selected simulations?
|
||||
simpanel.dlg.lbl.DeleteSim2 = <html><i>This operation cannot be undone.</i>
|
||||
simpanel.dlg.lbl.DeleteSim3 = Delete simulations
|
||||
simpanel.col.Status = Status
|
||||
simpanel.col.Name = Name
|
||||
simpanel.col.Motors = Motors
|
||||
simpanel.col.Configuration = Configuration
|
||||
@ -640,6 +645,8 @@ RK4SimulationStepper.error.valuesTooLarge = Simulation values exceeded limits.
|
||||
|
||||
SimulationModifierTree.OptimizationParameters = Optimization Parameters
|
||||
|
||||
SimulationStepper.error.totalMassZero = Total mass of active states is 0
|
||||
|
||||
! SimulationExportPanel
|
||||
SimExpPan.border.Vartoexport = Variables to export
|
||||
SimExpPan.border.Stage = Stage to export
|
||||
@ -1191,6 +1198,7 @@ InnerTubeCfg.tab.ttip.Radialpos = Radial position
|
||||
InnerTubeCfg.lbl.Selectclustercfg = Select cluster configuration:
|
||||
InnerTubeCfg.lbl.TubeSep = Tube separation:
|
||||
InnerTubeCfg.lbl.ttip.TubeSep = The separation of the tubes, 1.0 = touching each other
|
||||
InnerTubeCfg.lbl.ttip.TubeSepAbs = The separation of the tubes, 0 = touching each other
|
||||
InnerTubeCfg.lbl.Rotation = Rotation:
|
||||
InnerTubeCfg.lbl.ttip.Rotation = Rotation angle of the cluster configuration
|
||||
InnerTubeCfg.lbl.Rotangle = Rotation angle of the cluster configuration
|
||||
@ -1199,6 +1207,10 @@ InnerTubeCfg.lbl.longA1 = <html>Split the cluster into separate components.<br>
|
||||
InnerTubeCfg.lbl.longA2 = This also duplicates all components attached to this inner tube.
|
||||
InnerTubeCfg.but.Resetsettings = Reset settings
|
||||
InnerTubeCfg.but.ttip.Resetsettings = Reset the separation and rotation to the default values
|
||||
InnerTubeCfg.radioBut.Relative = Relative
|
||||
InnerTubeCfg.radioBut.Relative.ttip = The separation is measured relative to the outer diameter of the inner tube
|
||||
InnerTubeCfg.radioBut.Absolute = Absolute
|
||||
InnerTubeCfg.radioBut.Absolute.ttip = The separation is measured in length units
|
||||
|
||||
! LaunchLugConfig
|
||||
LaunchLugCfg.lbl.Length = Length:
|
||||
@ -2022,6 +2034,7 @@ Warning.TUBE_SEPARATION = Space between tube fins may not simulate accurately.
|
||||
Warning.TUBE_OVERLAP = Overlapping tube fins may not simulate accurately.
|
||||
Warning.EMPTY_BRANCH = Simulation branch contains no data
|
||||
Warning.SEPARATION_ORDER = Stages separated in an unreasonable order
|
||||
Warning.EARLY_SEPARATION = Stages separated before clearing launch rod/rail
|
||||
|
||||
! Scale dialog
|
||||
ScaleDialog.lbl.scaleRocket = Entire rocket
|
||||
|
||||
@ -92,9 +92,11 @@ public class CSVExport {
|
||||
private static void writeData(PrintWriter writer, FlightDataBranch branch,
|
||||
FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation,
|
||||
boolean eventComments, String commentStarter) {
|
||||
// Time variable
|
||||
List<Double> time = branch.get(FlightDataType.TYPE_TIME);
|
||||
|
||||
// Number of data points
|
||||
int n = branch.getLength();
|
||||
int n = time != null ? time.size() : branch.getLength();
|
||||
|
||||
// Flight events in occurrence order
|
||||
List<FlightEvent> events = branch.getEvents();
|
||||
@ -102,15 +104,14 @@ public class CSVExport {
|
||||
int eventPosition = 0;
|
||||
|
||||
// List of field values
|
||||
List<List<Double>> fieldValues = new ArrayList<List<Double>>();
|
||||
List<List<Double>> fieldValues = new ArrayList<>();
|
||||
for (FlightDataType t : fields) {
|
||||
fieldValues.add(branch.get(t));
|
||||
List<Double> values = branch.get(t);
|
||||
fieldValues.add(values);
|
||||
}
|
||||
|
||||
// Time variable
|
||||
List<Double> time = branch.get(FlightDataType.TYPE_TIME);
|
||||
// If time information is not available, print events at beginning of file
|
||||
if (eventComments && time == null) {
|
||||
// If time information is not available, print events at beginning of file
|
||||
for (FlightEvent e : events) {
|
||||
printEvent(writer, e, commentStarter);
|
||||
}
|
||||
|
||||
@ -216,21 +216,22 @@ public class OpenRocketSaver extends RocketSaver {
|
||||
/*
|
||||
* NOTE: Remember to update the supported versions in DocumentConfig as well!
|
||||
*
|
||||
* File version 1.8 is required for:
|
||||
* File version 1.9 is required for:
|
||||
* - new-style positioning
|
||||
* - external/parallel booster stages
|
||||
* - external pods
|
||||
* - Rail Buttons
|
||||
* - Flight event source saving
|
||||
*
|
||||
* Otherwise use version 1.8.
|
||||
* Otherwise use version 1.9.
|
||||
*/
|
||||
|
||||
/////////////////
|
||||
// Version 1.8 //
|
||||
// Version 1.9 //
|
||||
/////////////////
|
||||
// for any new-style positioning: 'axialoffset', 'angleoffset', 'radiusoffset' tags
|
||||
// these tags are used for any RocketComponent child classes positioning... so... ALL the classes.
|
||||
return FILE_VERSION_DIVISOR + 8;
|
||||
return FILE_VERSION_DIVISOR + 9;
|
||||
|
||||
}
|
||||
|
||||
@ -531,8 +532,13 @@ public class OpenRocketSaver extends RocketSaver {
|
||||
|
||||
// Write events
|
||||
for (FlightEvent event : branch.getEvents()) {
|
||||
writeln("<event time=\"" + TextUtil.doubleToString(event.getTime())
|
||||
+ "\" type=\"" + enumToXMLName(event.getType()) + "\"/>");
|
||||
String eventStr = "<event time=\"" + TextUtil.doubleToString(event.getTime())
|
||||
+ "\" type=\"" + enumToXMLName(event.getType());
|
||||
if (event.getSource() != null) {
|
||||
eventStr += "\" source=\"" + TextUtil.escapeXML(event.getSource().getID());
|
||||
}
|
||||
eventStr += "\"/>";
|
||||
writeln(eventStr);
|
||||
}
|
||||
|
||||
// Write the data
|
||||
|
||||
@ -52,7 +52,7 @@ import net.sf.openrocket.util.Reflection;
|
||||
class DocumentConfig {
|
||||
|
||||
/* Remember to update OpenRocketSaver as well! */
|
||||
public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8" };
|
||||
public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9" };
|
||||
|
||||
/**
|
||||
* Divisor used in converting an integer version to the point-represented version.
|
||||
@ -113,6 +113,8 @@ class DocumentConfig {
|
||||
// RocketComponent
|
||||
setters.put("RocketComponent:name", new StringSetter(
|
||||
Reflection.findMethod(RocketComponent.class, "setName", String.class)));
|
||||
setters.put("RocketComponent:id", new StringSetter(
|
||||
Reflection.findMethod(RocketComponent.class, "setID", String.class)));
|
||||
setters.put("RocketComponent:color", new ColorSetter(
|
||||
Reflection.findMethod(RocketComponent.class, "setColor", Color.class)));
|
||||
setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
|
||||
|
||||
@ -8,6 +8,8 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler;
|
||||
import net.sf.openrocket.file.simplesax.ElementHandler;
|
||||
import net.sf.openrocket.file.simplesax.PlainTextHandler;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.simulation.FlightDataBranch;
|
||||
import net.sf.openrocket.simulation.FlightDataType;
|
||||
import net.sf.openrocket.simulation.FlightEvent;
|
||||
@ -126,6 +128,8 @@ class FlightDataBranchHandler extends AbstractElementHandler {
|
||||
if (element.equals("event")) {
|
||||
double time;
|
||||
FlightEvent.Type type;
|
||||
String sourceID;
|
||||
RocketComponent source = null;
|
||||
|
||||
try {
|
||||
time = DocumentConfig.stringToDouble(attributes.get("time"));
|
||||
@ -139,8 +143,15 @@ class FlightDataBranchHandler extends AbstractElementHandler {
|
||||
warnings.add("Illegal event specification, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the event source
|
||||
Rocket rocket = context.getOpenRocketDocument().getRocket();
|
||||
sourceID = attributes.get("source");
|
||||
if (sourceID != null) {
|
||||
source = rocket.findComponent(sourceID);
|
||||
}
|
||||
|
||||
branch.addEvent(new FlightEvent(type, time));
|
||||
branch.addEvent(new FlightEvent(type, time, source));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ public class RocketComponentSaver {
|
||||
|
||||
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
|
||||
elements.add("<name>" + TextUtil.escapeXML(c.getName()) + "</name>");
|
||||
elements.add("<id>" + TextUtil.escapeXML(c.getID()) + "</id>");
|
||||
|
||||
ComponentPreset preset = c.getPresetComponent();
|
||||
if (preset != null) {
|
||||
|
||||
@ -386,7 +386,11 @@ public abstract class Warning extends Message {
|
||||
public static final Warning TUBE_SEPARATION = new Other(trans.get("Warning.TUBE_SEPARATION"));
|
||||
public static final Warning TUBE_OVERLAP = new Other(trans.get("Warning.TUBE_OVERLAP"));
|
||||
|
||||
/** A <code>Warning</code> that stage separation occurred at other than the last stage */
|
||||
public static final Warning SEPARATION_ORDER = new Other(trans.get("Warning.SEPARATION_ORDER"));
|
||||
|
||||
/** A <code>Warning</code> that stage separation occurred before the rocket cleared the launch rod or rail */
|
||||
public static final Warning EARLY_SEPARATION = new Other(trans.get("Warning.EARLY_SEPARATION"));
|
||||
|
||||
public static final Warning EMPTY_BRANCH = new Other(trans.get("Warning.EMPTY_BRANCH"));
|
||||
}
|
||||
|
||||
@ -170,14 +170,14 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
|
||||
* @return the previous stage in the rocket
|
||||
*/
|
||||
public AxialStage getUpperStage() {
|
||||
if( null == this.parent ) {
|
||||
if (this.parent == null) {
|
||||
return null;
|
||||
}else if(Rocket.class.isAssignableFrom(this.parent.getClass()) ){
|
||||
final int thisIndex = getStageNumber();
|
||||
if( 0 < thisIndex ){
|
||||
return (AxialStage)parent.getChild(thisIndex-1);
|
||||
} else if (Rocket.class.isAssignableFrom(this.parent.getClass())) {
|
||||
final int thisIndex = parent.getChildPosition(this);
|
||||
if (thisIndex > 0) {
|
||||
return (AxialStage) parent.getChild(thisIndex-1);
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
return this.parent.getStage();
|
||||
}
|
||||
return null;
|
||||
@ -207,10 +207,10 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
|
||||
public StageSeparationConfiguration getSeparationConfiguration() {
|
||||
FlightConfiguration flConfig = getRocket().getSelectedConfiguration();
|
||||
StageSeparationConfiguration sepConfig = getSeparationConfigurations().get(flConfig.getId());
|
||||
// to ensure the configuration is distinct, and we're not modifying the default
|
||||
// To ensure the configuration is distinct, and we're not modifying the default
|
||||
if ((sepConfig == getSeparationConfigurations().getDefault())
|
||||
&& (flConfig.getId() != FlightConfigurationId.DEFAULT_VALUE_FCID)) {
|
||||
sepConfig = new StageSeparationConfiguration();
|
||||
sepConfig = sepConfig.copy(flConfig.getId());
|
||||
getSeparationConfigurations().set(flConfig.getId(), sepConfig);
|
||||
}
|
||||
return sepConfig;
|
||||
|
||||
@ -170,6 +170,11 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
|
||||
" Please set setClusterConfiguration(ClusterConfiguration) instead.",
|
||||
new UnsupportedOperationException("InnerTube.setInstanceCount(..) on an"+this.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfter(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
|
||||
@ -177,14 +182,10 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
|
||||
* pack inside each other.
|
||||
*/
|
||||
public double getClusterScale() {
|
||||
mutex.verify();
|
||||
return clusterScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfter(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster scaling.
|
||||
* @see #getClusterScale()
|
||||
@ -203,6 +204,23 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
|
||||
clusterScale = scale;
|
||||
fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cluster scaling as an absolute distance measurement. A value of 0 indicates that the tubes are packed
|
||||
* touching each other, larger values separate the tubes and smaller values pack inside each other.
|
||||
*/
|
||||
public double getClusterScaleAbsolute() {
|
||||
return (getClusterScale() - 1) * getOuterRadius() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the absolute cluster scaling (in terms of distance).
|
||||
* @see #getClusterScaleAbsolute()
|
||||
*/
|
||||
public void setClusterScaleAbsolute(double scale) {
|
||||
double scaleRel = scale / (getOuterRadius() * 2) + 1;
|
||||
setClusterScale(scaleRel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ public abstract class MassObject extends InternalComponent {
|
||||
return radius;
|
||||
}
|
||||
if (parent instanceof NoseCone) {
|
||||
return ((NoseCone) parent).getAftRadius();
|
||||
return ((NoseCone) parent).getBaseRadius();
|
||||
} else if (parent instanceof Transition) {
|
||||
double foreRadius = ((Transition) parent).getForeRadius();
|
||||
double aftRadius = ((Transition) parent).getAftRadius();
|
||||
|
||||
@ -211,14 +211,15 @@ public class Parachute extends RecoveryDevice {
|
||||
|
||||
// // Set preset parachute packed length
|
||||
if ((preset.has(ComponentPreset.PACKED_LENGTH)) && preset.get(ComponentPreset.PACKED_LENGTH) > 0) {
|
||||
length = preset.get(ComponentPreset.PACKED_LENGTH);
|
||||
setLength(preset.get(ComponentPreset.PACKED_LENGTH));
|
||||
}
|
||||
// // Set preset parachute packed diameter
|
||||
if ((preset.has(ComponentPreset.PACKED_DIAMETER)) && preset.get(ComponentPreset.PACKED_DIAMETER) > 0) {
|
||||
radius = preset.get(ComponentPreset.PACKED_DIAMETER) / 2;
|
||||
setRadius(preset.get(ComponentPreset.PACKED_DIAMETER) / 2);
|
||||
}
|
||||
// // Size parachute packed diameter within parent inner diameter
|
||||
if (length > 0 && radius > 0) {
|
||||
if (preset.has(ComponentPreset.PACKED_LENGTH) && (getLength() > 0) &&
|
||||
preset.has(ComponentPreset.PACKED_DIAMETER) && (getRadius() > 0)) {
|
||||
setRadiusAutomatic(true);
|
||||
}
|
||||
|
||||
|
||||
@ -1282,8 +1282,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
mutex.verify();
|
||||
this.id = UniqueID.uuid();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set the ID for this component.
|
||||
* Generally not recommended to directly set the ID, this is done automatically. Only use this in case you have to.
|
||||
* @param newID new ID
|
||||
*/
|
||||
public void setID(String newID) {
|
||||
mutex.verify();
|
||||
this.id = newID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -2047,6 +2055,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this component contains <component> as one of its (sub-)children.
|
||||
* @param component component to check
|
||||
* @return true if component is a (sub-)child of this component
|
||||
*/
|
||||
public final boolean containsChild(RocketComponent component) {
|
||||
List<RocketComponent> allChildren = getAllChildren();
|
||||
return allChildren.contains(component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -3,10 +3,12 @@ package net.sf.openrocket.simulation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceMap;
|
||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.GeodeticComputationStrategy;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
@ -14,6 +16,7 @@ import net.sf.openrocket.util.WorldCoordinate;
|
||||
|
||||
public abstract class AbstractEulerStepper extends AbstractSimulationStepper {
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractEulerStepper.class);
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
private static final double RECOVERY_TIME_STEP = 0.5;
|
||||
|
||||
@ -46,12 +49,15 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper {
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = getCD() * dynP * status.getConfiguration().getReferenceArea();
|
||||
|
||||
// n.b. this is constant, and could be calculated once at the beginning of this simulation branch...
|
||||
double rocketMass = calculateStructureMass(status).getMass();
|
||||
double motorMass = calculateMotorMass(status).getMass();
|
||||
|
||||
double mass = rocketMass + motorMass;
|
||||
|
||||
if (mass < MathUtil.EPSILON) {
|
||||
throw new SimulationException(trans.get("SimulationStepper.error.totalMassZero"));
|
||||
}
|
||||
|
||||
// Compute drag acceleration
|
||||
Coordinate linearAcceleration;
|
||||
if (airSpeed.length() > 0.001) {
|
||||
|
||||
@ -485,11 +485,16 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
currentStatus.getWarnings().add(Warning.SEPARATION_ORDER);
|
||||
}
|
||||
|
||||
// If I haven't cleared the rail yet, flag a warning
|
||||
if (!currentStatus.isLaunchRodCleared()) {
|
||||
currentStatus.getWarnings().add(Warning.EARLY_SEPARATION);
|
||||
}
|
||||
|
||||
// Create a new simulation branch for the booster
|
||||
SimulationStatus boosterStatus = new SimulationStatus(currentStatus);
|
||||
|
||||
// Prepare the new simulation branch
|
||||
boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), currentStatus.getFlightData()));
|
||||
boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), boosterStage, currentStatus.getFlightData()));
|
||||
boosterStatus.getFlightData().addEvent(event);
|
||||
|
||||
// Mark the current status as having dropped the current stage and all stages below it
|
||||
|
||||
@ -134,8 +134,12 @@ public class FlightData {
|
||||
return branches.size();
|
||||
}
|
||||
|
||||
public FlightDataBranch getBranch(int n) {
|
||||
return branches.get(n);
|
||||
public FlightDataBranch getBranch(int stageNr) {
|
||||
return branches.get(stageNr);
|
||||
}
|
||||
|
||||
public int getStageNr(FlightDataBranch branch) {
|
||||
return branches.indexOf(branch);
|
||||
}
|
||||
|
||||
public List<FlightDataBranch> getBranches() {
|
||||
|
||||
@ -6,6 +6,9 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.util.ArrayList;
|
||||
import net.sf.openrocket.util.Monitorable;
|
||||
import net.sf.openrocket.util.Mutable;
|
||||
@ -30,11 +33,10 @@ public class FlightDataBranch implements Monitorable {
|
||||
/** The name of this flight data branch. */
|
||||
private final String branchName;
|
||||
|
||||
private final Map<FlightDataType, ArrayList<Double>> values =
|
||||
new LinkedHashMap<FlightDataType, ArrayList<Double>>();
|
||||
private final Map<FlightDataType, ArrayList<Double>> values = new LinkedHashMap<>();
|
||||
|
||||
private final Map<FlightDataType, Double> maxValues = new HashMap<FlightDataType, Double>();
|
||||
private final Map<FlightDataType, Double> minValues = new HashMap<FlightDataType, Double>();
|
||||
private final Map<FlightDataType, Double> maxValues = new HashMap<>();
|
||||
private final Map<FlightDataType, Double> minValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* time for the rocket to reach apogee if the flight had been no recovery deployment
|
||||
@ -77,23 +79,19 @@ public class FlightDataBranch implements Monitorable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a flight data branch with one data point copied from its parent. Intended for use
|
||||
* Make a flight data branch with all data points copied from its parent. Intended for use
|
||||
* when creating a new branch upon stage separation, so the data at separation is present
|
||||
* in both branches (and if the new branch has an immediate exception, it can be plotted)
|
||||
*
|
||||
* @param branchName the name of the new branch.
|
||||
* @param srcComponent the component that is the source of the new branch.
|
||||
* @param parent the parent branch to copy data from.
|
||||
*/
|
||||
public FlightDataBranch(String branchName, FlightDataBranch parent) {
|
||||
public FlightDataBranch(String branchName, RocketComponent srcComponent, FlightDataBranch parent) {
|
||||
this.branchName = branchName;
|
||||
|
||||
// need to have at least one type to set up values
|
||||
values.put(FlightDataType.TYPE_TIME, new ArrayList<Double>());
|
||||
minValues.put(FlightDataType.TYPE_TIME, Double.NaN);
|
||||
maxValues.put(FlightDataType.TYPE_TIME, Double.NaN);
|
||||
|
||||
// copy all values into new FlightDataBranch
|
||||
this.addPoint();
|
||||
for (FlightDataType t : parent.getTypes()) {
|
||||
this.setValue(t, parent.getLast(t));
|
||||
}
|
||||
// Copy all the values from the parent
|
||||
copyValuesFromBranch(parent, srcComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,12 +113,27 @@ public class FlightDataBranch implements Monitorable {
|
||||
public void addPoint() {
|
||||
mutable.check();
|
||||
|
||||
for (FlightDataType t : values.keySet()) {
|
||||
values.get(t).add(Double.NaN);
|
||||
for (FlightDataType type : values.keySet()) {
|
||||
sanityCheckValues(type, Double.NaN);
|
||||
values.get(type).add(Double.NaN);
|
||||
}
|
||||
modID++;
|
||||
}
|
||||
|
||||
|
||||
private void sanityCheckValues(FlightDataType type, Double value) {
|
||||
ArrayList<Double> list = values.get(type);
|
||||
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
int n = getLength();
|
||||
for (int i = 0; i < n; i++) {
|
||||
list.add(Double.NaN);
|
||||
}
|
||||
values.put(type, list);
|
||||
minValues.put(type, value);
|
||||
maxValues.put(type, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for a specific data type at the latest point. New variable types can be
|
||||
@ -132,20 +145,10 @@ public class FlightDataBranch implements Monitorable {
|
||||
*/
|
||||
public void setValue(FlightDataType type, double value) {
|
||||
mutable.check();
|
||||
|
||||
|
||||
sanityCheckValues(type, value);
|
||||
ArrayList<Double> list = values.get(type);
|
||||
|
||||
if (list == null) {
|
||||
list = new ArrayList<Double>();
|
||||
int n = getLength();
|
||||
for (int i = 0; i < n; i++) {
|
||||
list.add(Double.NaN);
|
||||
}
|
||||
values.put(type, list);
|
||||
minValues.put(type, value);
|
||||
maxValues.put(type, value);
|
||||
}
|
||||
|
||||
if (list.size() > 0) {
|
||||
list.set(list.size() - 1, value);
|
||||
}
|
||||
@ -161,7 +164,68 @@ public class FlightDataBranch implements Monitorable {
|
||||
}
|
||||
modID++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears all the current values in the branch and copies the values from the given branch.
|
||||
* @param srcBranch the branch to copy values from
|
||||
* @param srcComponent the component that is the source of this branch (used for copying events)
|
||||
*/
|
||||
private void copyValuesFromBranch(FlightDataBranch srcBranch, RocketComponent srcComponent) {
|
||||
this.values.clear();
|
||||
|
||||
// Need to have at least one type to set up values
|
||||
values.put(FlightDataType.TYPE_TIME, new ArrayList<>());
|
||||
minValues.put(FlightDataType.TYPE_TIME, Double.NaN);
|
||||
maxValues.put(FlightDataType.TYPE_TIME, Double.NaN);
|
||||
|
||||
if (srcBranch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy flight data
|
||||
for (int i = 0; i < srcBranch.getLength(); i++) {
|
||||
this.addPoint();
|
||||
for (FlightDataType type : srcBranch.getTypes()) {
|
||||
this.setValue(type, srcBranch.getByIndex(type, i));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy flight events belonging to this branch
|
||||
List<FlightEvent> sustainerEvents = srcBranch.getEvents();
|
||||
for (FlightEvent event : sustainerEvents) {
|
||||
// Stage separation is already added elsewhere, so don't copy it over (otherwise you have a duplicate)
|
||||
if (event.getType() == FlightEvent.Type.STAGE_SEPARATION) {
|
||||
continue;
|
||||
}
|
||||
RocketComponent srcEventComponent = event.getSource();
|
||||
// Ignore null events
|
||||
if (srcComponent == null || srcEventComponent == null) {
|
||||
continue;
|
||||
}
|
||||
// Ignore events from other stages. Important for when the current stage has a booster stage; we don't want to copy over the booster events.
|
||||
if (getStageForComponent(srcComponent) != getStageForComponent(srcEventComponent)) {
|
||||
continue;
|
||||
}
|
||||
if (srcComponent == srcEventComponent || srcComponent.containsChild(srcEventComponent)) {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safer method for checking the stage of a component (that shouldn't throw exceptions when calling on stages/rockets)
|
||||
* @param component the component to get the stage of
|
||||
* @return the stage of the component, or null if the component is a rocket
|
||||
*/
|
||||
private AxialStage getStageForComponent(RocketComponent component) {
|
||||
if (component instanceof AxialStage) {
|
||||
return (AxialStage) component;
|
||||
} else if (component instanceof Rocket) {
|
||||
return null;
|
||||
} else {
|
||||
return component.getStage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the branch name.
|
||||
@ -203,6 +267,23 @@ public class FlightDataBranch implements Monitorable {
|
||||
return null;
|
||||
return list.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the specified type at the specified index.
|
||||
* @param type the variable type
|
||||
* @param index the data index of the value
|
||||
* @return the value at the specified index
|
||||
*/
|
||||
public Double getByIndex(FlightDataType type, int index) {
|
||||
if (index < 0 || index >= getLength()) {
|
||||
throw new IllegalArgumentException("Index out of bounds");
|
||||
}
|
||||
ArrayList<Double> list = values.get(type);
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last value of the specified type in the branch, or NaN if the type is
|
||||
|
||||
@ -329,6 +329,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
store.motorMass = calculateMotorMass(status);
|
||||
store.rocketMass = structureMassData.add( store.motorMass );
|
||||
|
||||
if (store.rocketMass.getMass() < MathUtil.EPSILON) {
|
||||
throw new SimulationException(trans.get("SimulationStepper.error.totalMassZero"));
|
||||
}
|
||||
|
||||
// Calculate the forces from the aerodynamic coefficients
|
||||
|
||||
double dynP = (0.5 * store.flightConditions.getAtmosphericConditions().getDensity() *
|
||||
|
||||
@ -82,6 +82,7 @@ public abstract class Preferences implements ChangeSource {
|
||||
private static final String AUTO_OPEN_LAST_DESIGN = "AutoOpenLastDesign";
|
||||
private static final String OPEN_LEFTMOST_DESIGN_TAB = "OpenLeftmostDesignTab";
|
||||
private static final String SHOW_DISCARD_CONFIRMATION = "IgnoreDiscardEditingWarning";
|
||||
private static final String SHOW_SAVE_ROCKET_INFO = "ShowSaveRocketInfo";
|
||||
private static final String SHOW_DISCARD_SIMULATION_CONFIRMATION = "IgnoreDiscardSimulationEditingWarning";
|
||||
private static final String SHOW_DISCARD_PREFERENCES_CONFIRMATION = "IgnoreDiscardPreferencesWarning";
|
||||
public static final String MARKER_STYLE_ICON = "MarkerStyleIcon";
|
||||
@ -589,6 +590,21 @@ public abstract class Preferences implements ChangeSource {
|
||||
this.putBoolean(SHOW_DISCARD_CONFIRMATION, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a 'save rocket information' dialog should be shown after saving a new design file.
|
||||
* @return true if the 'save rocket information' dialog should be shown.
|
||||
*/
|
||||
public final boolean isShowSaveRocketInfo() {
|
||||
return this.getBoolean(SHOW_SAVE_ROCKET_INFO, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable showing a 'save rocket information' dialog after saving a new design file.
|
||||
* @return true if the 'save rocket information' dialog should be shown.
|
||||
*/
|
||||
public final void setShowSaveRocketInfo(boolean enabled) {
|
||||
this.putBoolean(SHOW_SAVE_ROCKET_INFO, enabled);
|
||||
}
|
||||
/**
|
||||
* Answer if a confirmation dialog should be shown when canceling a simulation config operation.
|
||||
*
|
||||
|
||||
@ -331,9 +331,9 @@ public class OpenRocketSaverTest {
|
||||
////////////////////////////////
|
||||
|
||||
@Test
|
||||
public void testFileVersion108_withSimulationExtension() {
|
||||
public void testFileVersion109_withSimulationExtension() {
|
||||
OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT);
|
||||
assertEquals(108, getCalculatedFileVersion(rocketDoc));
|
||||
assertEquals(109, getCalculatedFileVersion(rocketDoc));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -108,8 +108,8 @@ public class FlightEventsTest extends BaseTestCase {
|
||||
// events whose time is too variable to check are given a time of 1200
|
||||
for (int b = 0; b < 3; b++) {
|
||||
FlightEvent[] expectedEvents;
|
||||
final RocketComponent[] expectedSources;
|
||||
switch (b) {
|
||||
// Sustainer (payload fairing stage)
|
||||
case 0:
|
||||
expectedEvents = new FlightEvent[] {
|
||||
new FlightEvent(FlightEvent.Type.LAUNCH, 0.0, rocket),
|
||||
@ -129,15 +129,23 @@ public class FlightEventsTest extends BaseTestCase {
|
||||
new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
|
||||
};
|
||||
break;
|
||||
// Core stage
|
||||
case 1:
|
||||
expectedEvents = new FlightEvent[] {
|
||||
new FlightEvent(FlightEvent.Type.IGNITION, 0.0, coreBody),
|
||||
new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, coreBody),
|
||||
new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, coreStage),
|
||||
new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, coreStage),
|
||||
new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
|
||||
new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
|
||||
};
|
||||
break;
|
||||
// Booster stage
|
||||
case 2:
|
||||
expectedEvents = new FlightEvent[] {
|
||||
new FlightEvent(FlightEvent.Type.IGNITION, 0.0, boosterMotorTubes),
|
||||
new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, boosterMotorTubes),
|
||||
new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, boosterStage),
|
||||
new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage),
|
||||
new FlightEvent(FlightEvent.Type.TUMBLE, 3.551, null),
|
||||
new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
|
||||
@ -150,10 +158,7 @@ public class FlightEventsTest extends BaseTestCase {
|
||||
|
||||
// Test event count
|
||||
final FlightDataBranch branch = sim.getSimulatedData().getBranch(b);
|
||||
final FlightEvent[] events = (FlightEvent[]) branch.getEvents().toArray(new FlightEvent[0]);
|
||||
for (int i = 0; i < events.length; i++) {
|
||||
System.out.println("branch " + b + " index " + i + " event " + events[i]);
|
||||
}
|
||||
final FlightEvent[] events = branch.getEvents().toArray(new FlightEvent[0]);
|
||||
assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events ", expectedEvents.length, events.length);
|
||||
|
||||
// Test that all expected events are present, in the right order, at the right time, from the right sources
|
||||
|
||||
@ -63,3 +63,6 @@ The following file format versions exist:
|
||||
Rename <fincount> to <instancecount> (<fincount> remains for backward compatibility)
|
||||
Rename <position> to <axialoffset> (<position> remains for backward compatibility)
|
||||
Rename <rotation> to <angleoffset> (<rotation> remains for backward compatibility)
|
||||
|
||||
1.9: Introduced with OpenRocket 23.xx.
|
||||
Added ID for each rocket component, to in turn add this ID as a source for flight events.
|
||||
Binary file not shown.
@ -9,6 +9,8 @@ import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
@ -17,12 +19,14 @@ import java.util.EventObject;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ButtonGroup;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JRadioButton;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.BevelBorder;
|
||||
@ -47,6 +51,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
import net.sf.openrocket.rocketcomponent.RingComponent;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.startup.Preferences;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
import net.sf.openrocket.util.BugException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
@ -56,6 +61,9 @@ import net.sf.openrocket.util.StateChangeListener;
|
||||
public class InnerTubeConfig extends RocketComponentConfig {
|
||||
private static final long serialVersionUID = 7900041420864324470L;
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
private static final Preferences prefs = Application.getPreferences();
|
||||
|
||||
private static final String PREF_SEPARATION_RELATIVE = "InnerTubeSeparationRelative";
|
||||
|
||||
|
||||
public InnerTubeConfig(OpenRocketDocument d, RocketComponent c, JDialog parent) {
|
||||
@ -280,29 +288,88 @@ public class InnerTubeConfig extends RocketComponentConfig {
|
||||
//// The separation of the tubes, 1.0 = touching each other
|
||||
l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
|
||||
subPanel.add(l);
|
||||
DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
|
||||
|
||||
JSpinner spin = new JSpinner(dm.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
//// The separation of the tubes, 1.0 = touching each other
|
||||
spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
|
||||
subPanel.add(spin, "growx");
|
||||
order.add(((SpinnerEditor) spin.getEditor()).getTextField());
|
||||
//// Models
|
||||
final boolean useRelativeSeparation = prefs.getBoolean(PREF_SEPARATION_RELATIVE, true);
|
||||
final DoubleModel clusterScaleModelRel = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
|
||||
final DoubleModel clusterScaleModelAbs = new DoubleModel(component, "ClusterScaleAbsolute", 1, UnitGroup.UNITS_LENGTH);
|
||||
final DoubleModel clusterScaleModel = useRelativeSeparation ? clusterScaleModelRel : clusterScaleModelAbs;
|
||||
|
||||
BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
|
||||
//// The separation of the tubes, 1.0 = touching each other
|
||||
bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
|
||||
subPanel.add(bs, "skip,w 100lp, wrap");
|
||||
final String clusterScaleTtipRel = trans.get("InnerTubeCfg.lbl.ttip.TubeSep");
|
||||
final String clusterScaleTtipAbs = trans.get("InnerTubeCfg.lbl.ttip.TubeSepAbs");
|
||||
final String clusterScaleTtip = useRelativeSeparation ? clusterScaleTtipRel : clusterScaleTtipAbs;
|
||||
|
||||
JSpinner clusterScaleSpin = new JSpinner(clusterScaleModel.getSpinnerModel());
|
||||
clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
|
||||
clusterScaleSpin.setToolTipText(clusterScaleTtip);
|
||||
subPanel.add(clusterScaleSpin, "growx");
|
||||
order.add(((SpinnerEditor) clusterScaleSpin.getEditor()).getTextField());
|
||||
|
||||
UnitSelector clusterScaleUnit = new UnitSelector(clusterScaleModel);
|
||||
subPanel.add(clusterScaleUnit, "growx");
|
||||
|
||||
BasicSlider clusterScaleBs = new BasicSlider(clusterScaleModel.getSliderModel(0, 1, 4));
|
||||
subPanel.add(clusterScaleBs, "w 100lp, wrap");
|
||||
|
||||
// Relative/absolute separation
|
||||
JRadioButton rbRel = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Relative"));
|
||||
JRadioButton rbAbs = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Absolute"));
|
||||
rbRel.setToolTipText(trans.get("InnerTubeCfg.radioBut.Relative.ttip"));
|
||||
rbAbs.setToolTipText(trans.get("InnerTubeCfg.radioBut.Absolute.ttip"));
|
||||
ButtonGroup bg = new ButtonGroup();
|
||||
bg.add(rbRel);
|
||||
bg.add(rbAbs);
|
||||
subPanel.add(rbRel, "skip, spanx, split 2");
|
||||
subPanel.add(rbAbs, "wrap");
|
||||
|
||||
rbRel.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (e.getStateChange() == ItemEvent.DESELECTED)
|
||||
return;
|
||||
clusterScaleSpin.setModel(clusterScaleModelRel.getSpinnerModel());
|
||||
clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
|
||||
clusterScaleUnit.setModel(clusterScaleModelRel);
|
||||
clusterScaleBs.setModel(clusterScaleModelRel.getSliderModel(0, 1, 4));
|
||||
clusterScaleSpin.setToolTipText(clusterScaleTtipRel);
|
||||
|
||||
prefs.putBoolean(PREF_SEPARATION_RELATIVE, false);
|
||||
}
|
||||
});
|
||||
rbAbs.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (e.getStateChange() == ItemEvent.DESELECTED)
|
||||
return;
|
||||
DoubleModel radiusModelMin = new DoubleModel(component, "OuterRadius", -2, UnitGroup.UNITS_LENGTH);
|
||||
DoubleModel radiusModelMax = new DoubleModel(component, "OuterRadius", 6, UnitGroup.UNITS_LENGTH);
|
||||
|
||||
clusterScaleSpin.setModel(clusterScaleModelAbs.getSpinnerModel());
|
||||
clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
|
||||
clusterScaleUnit.setModel(clusterScaleModelAbs);
|
||||
clusterScaleBs.setModel(clusterScaleModelAbs.getSliderModel(radiusModelMin, radiusModelMax));
|
||||
clusterScaleSpin.setToolTipText(clusterScaleTtipAbs);
|
||||
|
||||
prefs.putBoolean(PREF_SEPARATION_RELATIVE, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Select the button by default
|
||||
if (prefs.getBoolean(PREF_SEPARATION_RELATIVE, true)) {
|
||||
rbRel.setSelected(true);
|
||||
} else {
|
||||
rbAbs.setSelected(true);
|
||||
}
|
||||
|
||||
// Rotation:
|
||||
l = new JLabel(trans.get("InnerTubeCfg.lbl.Rotation"));
|
||||
//// Rotation angle of the cluster configuration
|
||||
l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
|
||||
subPanel.add(l);
|
||||
dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
|
||||
DoubleModel dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
|
||||
-Math.PI, Math.PI);
|
||||
|
||||
spin = new JSpinner(dm.getSpinnerModel());
|
||||
JSpinner spin = new JSpinner(dm.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
//// Rotation angle of the cluster configuration
|
||||
spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
|
||||
@ -310,7 +377,7 @@ public class InnerTubeConfig extends RocketComponentConfig {
|
||||
order.add(((SpinnerEditor) spin.getEditor()).getTextField());
|
||||
|
||||
subPanel.add(new UnitSelector(dm), "growx");
|
||||
bs = new BasicSlider(dm.getSliderModel());
|
||||
BasicSlider bs = new BasicSlider(dm.getSliderModel());
|
||||
//// Rotation angle of the cluster configuration
|
||||
bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
|
||||
subPanel.add(bs, "w 100lp, wrap para");
|
||||
|
||||
@ -69,7 +69,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
protected final OpenRocketDocument document;
|
||||
protected final RocketComponent component;
|
||||
protected final JTabbedPane tabbedPane;
|
||||
protected final ComponentConfigDialog parent;
|
||||
protected final JDialog parent;
|
||||
protected boolean isNewComponent = false; // Checks whether this config dialog is editing an existing component, or a new one
|
||||
|
||||
private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
|
||||
@ -86,7 +86,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
private DescriptionArea componentInfo;
|
||||
private IconToggleButton infoBtn;
|
||||
|
||||
private JPanel buttonPanel;
|
||||
protected JPanel buttonPanel;
|
||||
protected JButton okButton;
|
||||
protected JButton cancelButton;
|
||||
private AppearancePanel appearancePanel = null;
|
||||
@ -102,11 +102,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
|
||||
this.document = document;
|
||||
this.component = component;
|
||||
if (parent instanceof ComponentConfigDialog) {
|
||||
this.parent = (ComponentConfigDialog) parent;
|
||||
} else {
|
||||
this.parent = null;
|
||||
}
|
||||
this.parent = parent;
|
||||
|
||||
// Check the listeners for the same type and massive status
|
||||
allSameType = true;
|
||||
@ -194,7 +190,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
/**
|
||||
* Add a section to the component configuration dialog that displays information about the component.
|
||||
*/
|
||||
private void addComponentInfo(JPanel buttonPanel) {
|
||||
protected void addComponentInfo(JPanel buttonPanel) {
|
||||
// Don't add the info panel if this is a multi-comp edit
|
||||
List<RocketComponent> listeners = component.getConfigListeners();
|
||||
if (listeners != null && listeners.size() > 0) {
|
||||
@ -273,14 +269,14 @@ public class RocketComponentConfig extends JPanel {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
// Don't do anything on cancel if you are editing an existing component, and it is not modified
|
||||
if (!isNewComponent && parent != null && !parent.isModified()) {
|
||||
ComponentConfigDialog.disposeDialog();
|
||||
if (!isNewComponent && parent != null && (parent instanceof ComponentConfigDialog && !((ComponentConfigDialog) parent).isModified())) {
|
||||
disposeDialog();
|
||||
return;
|
||||
}
|
||||
// Apply the cancel operation if set to auto discard in preferences
|
||||
if (!preferences.isShowDiscardConfirmation()) {
|
||||
ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared
|
||||
ComponentConfigDialog.disposeDialog();
|
||||
disposeDialog();
|
||||
document.undo();
|
||||
return;
|
||||
}
|
||||
@ -291,7 +287,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
trans.get("RocketCompCfg.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
if (resultYesNo == JOptionPane.YES_OPTION) {
|
||||
ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared
|
||||
ComponentConfigDialog.disposeDialog();
|
||||
disposeDialog();
|
||||
document.undo();
|
||||
}
|
||||
}
|
||||
@ -304,7 +300,7 @@ public class RocketComponentConfig extends JPanel {
|
||||
okButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
ComponentConfigDialog.disposeDialog();
|
||||
disposeDialog();
|
||||
}
|
||||
});
|
||||
buttonPanel.add(okButton);
|
||||
@ -314,7 +310,17 @@ public class RocketComponentConfig extends JPanel {
|
||||
this.add(buttonPanel, "newline, spanx, growx");
|
||||
}
|
||||
|
||||
private JPanel createCancelOperationContent() {
|
||||
protected void disposeDialog() {
|
||||
if (parent != null) {
|
||||
if (parent instanceof ComponentConfigDialog) {
|
||||
ComponentConfigDialog.disposeDialog();
|
||||
} else {
|
||||
parent.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected JPanel createCancelOperationContent() {
|
||||
JPanel panel = new JPanel(new MigLayout());
|
||||
String msg = isNewComponent ? trans.get("RocketCompCfg.CancelOperation.msg.undoAdd") :
|
||||
trans.get("RocketCompCfg.CancelOperation.msg.discardChanges");
|
||||
|
||||
@ -0,0 +1,104 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.document.OpenRocketDocument;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
import net.sf.openrocket.gui.widgets.SelectColorButton;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.startup.Preferences;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* This class is used to create a panel that is shown when a new design file is saved. It is used to fill in the design
|
||||
* information for the file.
|
||||
*/
|
||||
public class SaveDesignInfoPanel extends RocketConfig {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
private static final Preferences preferences = Application.getPreferences();
|
||||
|
||||
public SaveDesignInfoPanel(OpenRocketDocument d, RocketComponent c, JDialog parent) {
|
||||
super(d, c, parent);
|
||||
|
||||
// (Optional) Fill in the design information for this file
|
||||
StyledLabel label = new StyledLabel(trans.get("SaveDesignInfoPanel.lbl.FillInInfo"), StyledLabel.Style.BOLD);
|
||||
this.add(label, "spanx, wrap para", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addButtons(JButton... buttons) {
|
||||
if (buttonPanel != null) {
|
||||
this.remove(buttonPanel);
|
||||
}
|
||||
|
||||
buttonPanel = new JPanel(new MigLayout("fill, ins 5, hidemode 3"));
|
||||
|
||||
//// Don't show this dialog again
|
||||
JCheckBox dontShowAgain = new JCheckBox(trans.get("welcome.dlg.checkbox.dontShowAgain"));
|
||||
dontShowAgain.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
preferences.setShowSaveRocketInfo(!((JCheckBox) e.getSource()).isSelected());
|
||||
}
|
||||
});
|
||||
buttonPanel.add(dontShowAgain, "gapright 10, growx");
|
||||
|
||||
//// Cancel button
|
||||
this.cancelButton = new SelectColorButton(trans.get("dlg.but.cancel"));
|
||||
this.cancelButton.setToolTipText(trans.get("RocketCompCfg.btn.Cancel.ttip"));
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
// Don't do anything on cancel if you are editing an existing component, and it is not modified
|
||||
if (!isNewComponent && parent != null && (parent instanceof ComponentConfigDialog && !((ComponentConfigDialog) parent).isModified())) {
|
||||
disposeDialog();
|
||||
return;
|
||||
}
|
||||
// Apply the cancel operation if set to auto discard in preferences
|
||||
if (!preferences.isShowDiscardConfirmation()) {
|
||||
ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared
|
||||
disposeDialog();
|
||||
document.undo();
|
||||
return;
|
||||
}
|
||||
|
||||
// Yes/No dialog: Are you sure you want to discard your changes?
|
||||
JPanel msg = createCancelOperationContent();
|
||||
int resultYesNo = JOptionPane.showConfirmDialog(SaveDesignInfoPanel.this, msg,
|
||||
trans.get("RocketCompCfg.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
if (resultYesNo == JOptionPane.YES_OPTION) {
|
||||
ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared
|
||||
disposeDialog();
|
||||
document.undo();
|
||||
}
|
||||
}
|
||||
});
|
||||
buttonPanel.add(cancelButton, "split 2, right, gapleft 30lp");
|
||||
|
||||
//// Ok button
|
||||
this.okButton = new SelectColorButton(trans.get("dlg.but.ok"));
|
||||
this.okButton.setToolTipText(trans.get("RocketCompCfg.btn.OK.ttip"));
|
||||
okButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
disposeDialog();
|
||||
}
|
||||
});
|
||||
buttonPanel.add(okButton);
|
||||
|
||||
this.add(buttonPanel, "newline, spanx, growx");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFields() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@ -226,25 +226,31 @@ public class RocketInfo implements FigureElement {
|
||||
Rectangle2D stabTextRect = stabText.getVisualBounds();
|
||||
Rectangle2D atTextRect = atText.getVisualBounds();
|
||||
|
||||
double unitWidth = MathUtil.max(cpRect.getWidth(), cgRect.getWidth(), stabRect.getWidth());
|
||||
double unitWidth = MathUtil.max(cpRect.getWidth(), cgRect.getWidth());
|
||||
double stabUnitWidth = stabRect.getWidth();
|
||||
double textWidth = Math.max(cpTextRect.getWidth(), cgTextRect.getWidth());
|
||||
|
||||
// Add an extra space worth of width so the text doesn't run into the values
|
||||
unitWidth = unitWidth + spaceWidth;
|
||||
stabUnitWidth = stabUnitWidth + spaceWidth;
|
||||
|
||||
g2.setColor(GUIUtil.getUITheme().getTextColor());
|
||||
|
||||
// Draw the stability, CG & CP values (and units)
|
||||
g2.drawGlyphVector(stabValue, (float)(x2-stabRect.getWidth()), y1);
|
||||
g2.drawGlyphVector(cgValue, (float)(x2-cgRect.getWidth()), y1+line);
|
||||
g2.drawGlyphVector(cpValue, (float)(x2-cpRect.getWidth()), y1+2*line);
|
||||
|
||||
g2.drawGlyphVector(stabText, (float)(x2-unitWidth-stabTextRect.getWidth()), y1);
|
||||
// Draw the stability, CG & CP labels
|
||||
g2.drawGlyphVector(stabText, (float)(x2-stabUnitWidth-stabTextRect.getWidth()), y1);
|
||||
g2.drawGlyphVector(cgText, (float)(x2-unitWidth-cgTextRect.getWidth()), y1+line);
|
||||
g2.drawGlyphVector(cpText, (float)(x2-unitWidth-cpTextRect.getWidth()), y1+2*line);
|
||||
|
||||
|
||||
// Draw the CG caret
|
||||
cgCaret.setPosition(x2 - unitWidth - textWidth - 10, y1+line-0.3*line);
|
||||
cgCaret.paint(g2, 1.7);
|
||||
|
||||
// Draw the CP caret
|
||||
cpCaret.setPosition(x2 - unitWidth - textWidth - 10, y1+2*line-0.3*line);
|
||||
cpCaret.paint(g2, 1.7);
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ import javax.swing.tree.DefaultTreeSelectionModel;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.configdialog.SaveDesignInfoPanel;
|
||||
import net.sf.openrocket.gui.dialogs.ErrorWarningDialog;
|
||||
import net.sf.openrocket.logging.ErrorSet;
|
||||
import net.sf.openrocket.logging.WarningSet;
|
||||
@ -1596,6 +1597,9 @@ public class BasicFrame extends JFrame {
|
||||
* @return true if the file was saved, false otherwise
|
||||
*/
|
||||
private boolean saveAsAction() {
|
||||
// Open dialog for saving rocket info
|
||||
showSaveRocketInfoDialog();
|
||||
|
||||
File file = openFileSaveAsDialog(FileType.OPENROCKET);
|
||||
if (file == null) {
|
||||
return false;
|
||||
@ -1610,6 +1614,25 @@ public class BasicFrame extends JFrame {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void showSaveRocketInfoDialog() {
|
||||
if (!prefs.isShowSaveRocketInfo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the rocket in the component tree to indicate to users that they can edit the rocket info by editing the rocket
|
||||
setSelectedComponent(rocket);
|
||||
|
||||
// Open the save rocket info
|
||||
JDialog dialog = new JDialog();
|
||||
SaveDesignInfoPanel panel = new SaveDesignInfoPanel(document, rocket, dialog);
|
||||
dialog.setContentPane(panel);
|
||||
dialog.pack();
|
||||
dialog.setTitle(trans.get("BasicFrame.lbl.SaveRocketInfo"));
|
||||
dialog.setModal(true);
|
||||
dialog.setLocationRelativeTo(null);
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform the writing of the design to the given file in OpenRocket format.
|
||||
|
||||
@ -111,6 +111,7 @@ public class SimulationPanel extends JPanel {
|
||||
private final JPopupMenu pm;
|
||||
|
||||
private final SimulationAction editSimulationAction;
|
||||
private final SimulationAction copyValuesSimulationAction;
|
||||
private final SimulationAction runSimulationAction;
|
||||
private final SimulationAction plotSimulationAction;
|
||||
private final SimulationAction duplicateSimulationAction;
|
||||
@ -130,6 +131,7 @@ public class SimulationPanel extends JPanel {
|
||||
// Simulation actions
|
||||
SimulationAction newSimulationAction = new NewSimulationAction();
|
||||
editSimulationAction = new EditSimulationAction();
|
||||
copyValuesSimulationAction = new CopyValuesSimulationAction();
|
||||
runSimulationAction = new RunSimulationAction();
|
||||
plotSimulationAction = new PlotSimulationAction();
|
||||
duplicateSimulationAction = new DuplicateSimulationAction();
|
||||
@ -156,7 +158,7 @@ public class SimulationPanel extends JPanel {
|
||||
RocketActions.tieActionToButton(runButton, runSimulationAction, trans.get("simpanel.but.runsimulations"));
|
||||
runButton.setToolTipText(trans.get("simpanel.but.ttip.runsimu"));
|
||||
this.add(runButton, "gapright para");
|
||||
|
||||
|
||||
//// Delete simulations button
|
||||
deleteButton = new IconButton();
|
||||
RocketActions.tieActionToButton(deleteButton, deleteSimulationAction, trans.get("simpanel.but.deletesimulations"));
|
||||
@ -185,10 +187,12 @@ public class SimulationPanel extends JPanel {
|
||||
simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
|
||||
simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
|
||||
simulationTable.setFillsViewportHeight(true);
|
||||
simulationTable.registerKeyboardAction(copyValuesSimulationAction, "Copy", RocketActions.COPY_KEY_STROKE, JComponent.WHEN_FOCUSED);
|
||||
|
||||
// Context menu
|
||||
pm = new JPopupMenu();
|
||||
pm.add(editSimulationAction);
|
||||
pm.add(copyValuesSimulationAction);
|
||||
pm.add(duplicateSimulationAction);
|
||||
pm.add(deleteSimulationAction);
|
||||
pm.addSeparator();
|
||||
@ -481,36 +485,40 @@ public class SimulationPanel extends JPanel {
|
||||
}
|
||||
|
||||
|
||||
private void copySimulationAction() {
|
||||
int numCols=simulationTable.getColumnCount();
|
||||
int numRows=simulationTable.getSelectedRowCount();
|
||||
int[] rowsSelected=simulationTable.getSelectedRows();
|
||||
|
||||
if (numRows!=rowsSelected[rowsSelected.length-1]-rowsSelected[0]+1 || numRows!=rowsSelected.length) {
|
||||
private void copySimulationValuesAction() {
|
||||
int numCols = simulationTable.getColumnCount();
|
||||
int numRows = simulationTable.getSelectedRowCount();
|
||||
int[] rowsSelected = simulationTable.getSelectedRows();
|
||||
|
||||
if (numRows != (rowsSelected[rowsSelected.length-1] - rowsSelected[0] + 1) || numRows != rowsSelected.length) {
|
||||
JOptionPane.showMessageDialog(null, "Invalid Copy Selection", "Invalid Copy Selection", JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder excelStr =new StringBuilder();
|
||||
for (int k = 1; k < numCols; k++) {
|
||||
excelStr.append(simulationTable.getColumnName(k));
|
||||
if (k < numCols-1) {
|
||||
excelStr.append("\t");
|
||||
StringBuilder valuesStr = new StringBuilder();
|
||||
|
||||
// Copy the column names
|
||||
valuesStr.append(trans.get("simpanel.col.Status")).append("\t");
|
||||
for (int i = 1; i < numCols; i++) {
|
||||
valuesStr.append(simulationTable.getColumnName(i));
|
||||
if (i < numCols-1) {
|
||||
valuesStr.append("\t");
|
||||
}
|
||||
}
|
||||
excelStr.append("\n");
|
||||
valuesStr.append("\n");
|
||||
|
||||
// Copy the values
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
for (int j = 1; j < numCols; j++) {
|
||||
excelStr.append(simulationTable.getValueAt(rowsSelected[i], j));
|
||||
for (int j = 0; j < numCols; j++) {
|
||||
valuesStr.append(simulationTable.getValueAt(rowsSelected[i], j).toString());
|
||||
if (j < numCols-1) {
|
||||
excelStr.append("\t");
|
||||
valuesStr.append("\t");
|
||||
}
|
||||
}
|
||||
excelStr.append("\n");
|
||||
valuesStr.append("\n");
|
||||
}
|
||||
|
||||
StringSelection sel = new StringSelection(excelStr.toString());
|
||||
StringSelection sel = new StringSelection(valuesStr.toString());
|
||||
|
||||
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
cb.setContents(sel, sel);
|
||||
@ -546,6 +554,7 @@ public class SimulationPanel extends JPanel {
|
||||
|
||||
private void updateButtonStates() {
|
||||
editSimulationAction.updateEnabledState();
|
||||
copyValuesSimulationAction.updateEnabledState();
|
||||
deleteSimulationAction.updateEnabledState();
|
||||
runSimulationAction.updateEnabledState();
|
||||
plotSimulationAction.updateEnabledState();
|
||||
@ -596,6 +605,61 @@ public class SimulationPanel extends JPanel {
|
||||
return simulationTable.getSelectionModel();
|
||||
}
|
||||
|
||||
private String getSimulationToolTip(Simulation sim, boolean includeSimName) {
|
||||
String tip;
|
||||
FlightData data = sim.getSimulatedData();
|
||||
|
||||
tip = "<html>";
|
||||
if (includeSimName) {
|
||||
tip += "<b>" + sim.getName() + "</b><br>";
|
||||
}
|
||||
switch (sim.getStatus()) {
|
||||
case CANT_RUN:
|
||||
tip += trans.get("simpanel.ttip.noData")+"<br>";
|
||||
break;
|
||||
case LOADED:
|
||||
tip += trans.get("simpanel.ttip.loaded") + "<br>";
|
||||
break;
|
||||
case UPTODATE:
|
||||
tip += trans.get("simpanel.ttip.uptodate") + "<br>";
|
||||
break;
|
||||
|
||||
case OUTDATED:
|
||||
tip += trans.get("simpanel.ttip.outdated") + "<br>";
|
||||
break;
|
||||
|
||||
case EXTERNAL:
|
||||
tip += trans.get("simpanel.ttip.external") + "<br>";
|
||||
return tip;
|
||||
|
||||
case NOT_SIMULATED:
|
||||
tip += trans.get("simpanel.ttip.notSimulated");
|
||||
return tip;
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
tip += trans.get("simpanel.ttip.noData");
|
||||
return tip;
|
||||
}
|
||||
WarningSet warnings = data.getWarningSet();
|
||||
|
||||
if (warnings.isEmpty()) {
|
||||
tip += trans.get("simpanel.ttip.noWarnings");
|
||||
return tip;
|
||||
}
|
||||
|
||||
tip += trans.get("simpanel.ttip.warnings");
|
||||
for (Warning w : warnings) {
|
||||
tip += "<br>" + w.toString();
|
||||
}
|
||||
|
||||
return tip;
|
||||
}
|
||||
|
||||
private String getSimulationToolTip(Simulation sim) {
|
||||
return getSimulationToolTip(sim, true);
|
||||
}
|
||||
|
||||
private void openDialog(boolean plotMode, boolean isNewSimulation, final Simulation... sims) {
|
||||
SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, isNewSimulation, sims);
|
||||
if (plotMode) {
|
||||
@ -671,6 +735,25 @@ public class SimulationPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
class CopyValuesSimulationAction extends SimulationAction {
|
||||
public CopyValuesSimulationAction() {
|
||||
putValue(NAME, trans.get("simpanel.pop.copyValues"));
|
||||
this.putValue(MNEMONIC_KEY, KeyEvent.VK_C);
|
||||
this.putValue(ACCELERATOR_KEY, RocketActions.COPY_KEY_STROKE);
|
||||
this.putValue(SMALL_ICON, Icons.EDIT_COPY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
copySimulationValuesAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEnabledState() {
|
||||
setEnabled(simulationTable.getSelectedRowCount() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
class RunSimulationAction extends SimulationAction {
|
||||
public RunSimulationAction() {
|
||||
putValue(NAME, trans.get("simpanel.pop.run"));
|
||||
@ -853,53 +936,24 @@ public class SimulationPanel extends JPanel {
|
||||
}
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
private String getSimulationToolTip(Simulation sim) {
|
||||
String tip;
|
||||
FlightData data = sim.getSimulatedData();
|
||||
private class StatusLabel extends StyledLabel {
|
||||
private Simulation simulation;
|
||||
|
||||
tip = "<html><b>" + sim.getName() + "</b><br>";
|
||||
switch (sim.getStatus()) {
|
||||
case CANT_RUN:
|
||||
tip += trans.get("simpanel.ttip.noData")+"<br>";
|
||||
break;
|
||||
case LOADED:
|
||||
tip += trans.get("simpanel.ttip.loaded") + "<br>";
|
||||
break;
|
||||
case UPTODATE:
|
||||
tip += trans.get("simpanel.ttip.uptodate") + "<br>";
|
||||
break;
|
||||
public StatusLabel(Simulation simulation, float size) {
|
||||
super(size);
|
||||
this.simulation = simulation;
|
||||
}
|
||||
|
||||
case OUTDATED:
|
||||
tip += trans.get("simpanel.ttip.outdated") + "<br>";
|
||||
break;
|
||||
public void replaceSimulation(Simulation simulation) {
|
||||
this.simulation = simulation;
|
||||
}
|
||||
|
||||
case EXTERNAL:
|
||||
tip += trans.get("simpanel.ttip.external") + "<br>";
|
||||
return tip;
|
||||
|
||||
case NOT_SIMULATED:
|
||||
tip += trans.get("simpanel.ttip.notSimulated");
|
||||
return tip;
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
tip += trans.get("simpanel.ttip.noData");
|
||||
return tip;
|
||||
}
|
||||
WarningSet warnings = data.getWarningSet();
|
||||
|
||||
if (warnings.isEmpty()) {
|
||||
tip += trans.get("simpanel.ttip.noWarnings");
|
||||
return tip;
|
||||
}
|
||||
|
||||
tip += trans.get("simpanel.ttip.warnings");
|
||||
for (Warning w : warnings) {
|
||||
tip += "<br>" + w.toString();
|
||||
}
|
||||
|
||||
return tip;
|
||||
@Override
|
||||
public String toString() {
|
||||
String text = getSimulationToolTip(simulation, false);
|
||||
return text.replace("<br>", "-").replaceAll("<[^>]*>","");
|
||||
}
|
||||
}
|
||||
|
||||
@ -910,31 +964,33 @@ public class SimulationPanel extends JPanel {
|
||||
super(
|
||||
//// Status and warning column
|
||||
new Column("") {
|
||||
private JLabel label = null;
|
||||
private StatusLabel label = null;
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row) {
|
||||
if (row < 0 || row >= document.getSimulationCount())
|
||||
return null;
|
||||
|
||||
Simulation simulation = document.getSimulation(row);
|
||||
|
||||
// Initialize the label
|
||||
if (label == null) {
|
||||
label = new StyledLabel(2f);
|
||||
label = new StatusLabel(simulation, 2f);
|
||||
label.setIconTextGap(1);
|
||||
// label.setFont(label.getFont().deriveFont(Font.BOLD));
|
||||
} else {
|
||||
label.replaceSimulation(simulation);
|
||||
}
|
||||
|
||||
// Set simulation status icon
|
||||
Simulation.Status status = document.getSimulation(row).getStatus();
|
||||
Simulation.Status status = simulation.getStatus();
|
||||
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
|
||||
|
||||
|
||||
// Set warning marker
|
||||
if (status == Simulation.Status.NOT_SIMULATED ||
|
||||
status == Simulation.Status.EXTERNAL) {
|
||||
|
||||
label.setText("");
|
||||
|
||||
} else {
|
||||
|
||||
WarningSet w = document.getSimulation(row).getSimulatedWarnings();
|
||||
|
||||
@ -181,10 +181,13 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
|
||||
}
|
||||
|
||||
boolean update = false;
|
||||
AxialStage initStage = stages.get(0);
|
||||
FlightConfigurationId initFcId = fcIds.get(0);
|
||||
AxialStage initStage = stages.get(0); // Arbitrary choice of stage (all stages should have the same settings due to multi-comp editing)
|
||||
FlightConfigurationId initFcId = rocket.getSelectedConfiguration().getId(); // The SeparationSelectionDialog should apply its separation settings to the selected configuration
|
||||
|
||||
// Store the initial configuration so we can check later whether something changed
|
||||
StageSeparationConfiguration initialConfig = initStage.getSeparationConfigurations().get(initFcId).copy(initFcId);
|
||||
|
||||
// Launch the separation config dialog
|
||||
JDialog d = new SeparationSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, initStage);
|
||||
d.setVisible(true);
|
||||
|
||||
@ -195,6 +198,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
|
||||
double separationDelay = initStage.getSeparationConfigurations().get(initFcId).getSeparationDelay();
|
||||
SeparationEvent separationEvent= initStage.getSeparationConfigurations().get(initFcId).getSeparationEvent();
|
||||
|
||||
// Parse all stages anc flight configurations to check whether we need to update
|
||||
for (int i = 0; i < stages.size(); i++) {
|
||||
for (int j = 0; j < fcIds.size(); j++) {
|
||||
if ((i == 0) && (j == 0)) break;
|
||||
@ -205,7 +209,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
|
||||
initialConfig = config.copy(fcId);
|
||||
|
||||
if (stage.getSeparationConfigurations().isDefault(config)) {
|
||||
config = config.clone();
|
||||
config = config.copy(fcId);
|
||||
}
|
||||
|
||||
config.setSeparationDelay(separationDelay);
|
||||
|
||||
@ -192,9 +192,8 @@ public class SimulationPlot {
|
||||
}
|
||||
data[axis].addSeries(series);
|
||||
}
|
||||
// For each of the secondary branches, we use data from branch 0 for the earlier times
|
||||
// Secondary branches
|
||||
for (int branchIndex = 1; branchIndex < branchCount; branchIndex++) {
|
||||
FlightDataBranch primaryBranch = simulation.getSimulatedData().getBranch(0);
|
||||
FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchIndex);
|
||||
|
||||
// Ignore empty branches
|
||||
@ -206,25 +205,10 @@ public class SimulationPlot {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get first time index used in secondary branch;
|
||||
double firstSampleTime = thisBranch.get(FlightDataType.TYPE_TIME).get(0);
|
||||
|
||||
XYSeries series = new XYSeries(seriesCount++, false, true);
|
||||
series.setDescription(thisBranch.getBranchName() + ": " + name);
|
||||
|
||||
// Copy the first points from the primaryBranch.
|
||||
List<Double> primaryT = primaryBranch.get(FlightDataType.TYPE_TIME);
|
||||
List<Double> primaryx = primaryBranch.get(domainType);
|
||||
List<Double> primaryy = primaryBranch.get(type);
|
||||
|
||||
for (int j = 0; j < primaryT.size(); j++) {
|
||||
if (primaryT.get(j) >= firstSampleTime) {
|
||||
break;
|
||||
}
|
||||
series.add(domainUnit.toUnit(primaryx.get(j)), unit.toUnit(primaryy.get(j)));
|
||||
}
|
||||
|
||||
// Now copy all the data from the secondary branch
|
||||
// Copy all the data from the secondary branch
|
||||
List<Double> plotx = thisBranch.get(domainType);
|
||||
List<Double> ploty = thisBranch.get(type);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user