Merge branch 'kruland-integration' of github.com:plaa/openrocket into kruland-integration-ui

This commit is contained in:
Sampo Niskanen 2013-03-17 11:13:50 +02:00
commit 83fcf0894e
24 changed files with 281 additions and 142 deletions

View File

@ -1,4 +1,8 @@
2013-03-01 Kevin Ruland
* Added simulation of tumble recovery based on experimentation done by Sampo Niskane. This is particularly useful
for low power staged flights.
2012-10-01 Kevin Ruland
* Extended "motor configuration" concept to cover more properties. The concept was renamed "Flight Configuration" and
allows the user to override stage separation, recovery deployment, and motor ignition per flight configuration.
@ -7,7 +11,7 @@
usable. The user selects the configuration in a drop down with buttons for "add", "delete" and "rename". After selecting
the configuration, the tabbed area below allows the user to change the configuration for this simulation.
2012-10-01 Kevin Rualnd
2012-10-01 Kevin Ruland
* Allow simulation of stages without a motor. Users in the past have attempted to simulate separate recovery of payload
and booster by tricking OpenRocket using a dummy motor with trivial thrust curve. Now the user does not need to do this.
There is an example of this provided in TARC Payloader.ork and Boosted Dart.ork
@ -22,7 +26,6 @@
scroll with mouse wheel. If the alt key is used with either of these, only the domain is zoomed. Richard
contributed a more logical mouse controlled zoom - right click and drag will zoom (either domain, range or both).
2012-09-28 Sampo Niskane
* Released version 12.09.1

View File

@ -43,3 +43,7 @@ The following file format versions exist:
Added lowerstageseparation as recovery device deployment event.
Added <datatypes> section for supporting datatypes other than
internal ones. Currently only supports datatypes from custom expressions.
1.6: Introduced with OpenRocket 13.03. Added component Appearances (decals & paint)
Added configurable parameters to recovery devices, motor ignition and separation.

View File

@ -1383,6 +1383,7 @@ FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment
FlightEvent.Type.GROUND_HIT = Ground hit
FlightEvent.Type.SIMULATION_END = Simulation end
FlightEvent.Type.ALTITUDE = Altitude change
FlightEvent.Type.TUMBLE = Tumbling
! ThrustCurveMotorColumns
TCurveMotorCol.MANUFACTURER = Manufacturer

View File

@ -205,7 +205,7 @@ public class OpenRocketSaver extends RocketSaver {
private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
/*
* File version 1.6 is required for:
* - saving files using appearances and textures
* - saving files using appearances and textures, flight configurations.
*
* File version 1.5 is requires for:
* - saving designs using ComponentPrests
@ -223,11 +223,12 @@ public class OpenRocketSaver extends RocketSaver {
* Otherwise use version 1.0.
*/
// Search the rocket for any Appearnces (version 1.6)
// Search the rocket for any Appearances or non-motor flight configurations (version 1.6)
for (RocketComponent c : document.getRocket()) {
if (c.getAppearance() != null) {
return FILE_VERSION_DIVISOR + 6;
}
// FIXME - test for flight configurations
}
// Search the rocket for any ComponentPresets (version 1.5)
@ -447,7 +448,6 @@ public class OpenRocketSaver extends RocketSaver {
}
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
throws IOException {
double previousTime = -100000;

View File

@ -1,112 +0,0 @@
package net.sf.openrocket.gui.dialogs;
import java.awt.Dialog;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.main.ExampleDesignFile;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
public class ExampleDesignDialog extends JDialog {
private static final Translator trans = Application.getTranslator();
private boolean open = false;
private final JList designSelection;
private ExampleDesignDialog(ExampleDesignFile[] designs, Window parent) {
//// Open example design
super(parent, trans.get("exdesigndlg.lbl.Openexampledesign"), Dialog.ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
//// Select example designs to open:
panel.add(new JLabel(trans.get("exdesigndlg.lbl.Selectexample")), "wrap");
designSelection = new JList(designs);
designSelection.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
designSelection.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() >= 2) {
open = true;
ExampleDesignDialog.this.setVisible(false);
}
}
});
panel.add(new JScrollPane(designSelection), "grow, wmin 300lp, wrap para");
//// Open button
JButton openButton = new JButton(trans.get("exdesigndlg.but.open"));
openButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
open = true;
ExampleDesignDialog.this.setVisible(false);
}
});
panel.add(openButton, "split 2, sizegroup buttons, growx");
//// Cancel button
JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
open = false;
ExampleDesignDialog.this.setVisible(false);
}
});
panel.add(cancelButton, "sizegroup buttons, growx");
this.add(panel);
this.pack();
this.setLocationByPlatform(true);
GUIUtil.setDisposableDialogOptions(this, openButton);
}
/**
* Open a dialog to allow opening the example designs.
*
* @param parent the parent window of the dialog.
* @return an array of URL's to open, or <code>null</code> if the operation
* was cancelled.
*/
public static URL[] selectExampleDesigns(Window parent) {
ExampleDesignFile[] designs = ExampleDesignFile.getExampleDesigns();
ExampleDesignDialog dialog = new ExampleDesignDialog(designs, parent);
dialog.setVisible(true);
if (!dialog.open) {
return null;
}
Object[] selected = dialog.designSelection.getSelectedValues();
URL[] urls = new URL[selected.length];
int i = 0;
for (Object obj : selected) {
ExampleDesignFile file = (ExampleDesignFile) obj;
urls[i++] = file.getURL();
}
return urls;
}
}

View File

@ -73,7 +73,6 @@ import net.sf.openrocket.gui.dialogs.BugReportDialog;
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
import net.sf.openrocket.gui.dialogs.DebugLogDialog;
import net.sf.openrocket.gui.dialogs.DetailDialog;
import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.PrintDialog;
import net.sf.openrocket.gui.dialogs.ScaleDialog;
@ -429,34 +428,12 @@ public class BasicFrame extends JFrame {
item.setIcon(Icons.FILE_OPEN);
menu.add(item);
// FIXME - one of these two example menu items needs to be removed.
//// Open example...
item = new ExampleDesignFileAction(trans.get("main.menu.file.openExample"), this);
item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openexamplerocketdesign"));
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
menu.add(item);
//// Open example...
item = new JMenuItem(trans.get("main.menu.file.openExample"));
item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openexamplerocketdesign"));
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
SHORTCUT_KEY | ActionEvent.SHIFT_MASK));
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.user("Open example... selected");
URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this);
if (urls != null) {
for (URL u : urls) {
log.user("Opening example " + u);
open(u, BasicFrame.this);
}
}
}
});
menu.add(item);
menu.addSeparator();
//// Save

View File

@ -36,6 +36,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Total motion vs. time
@ -49,6 +50,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Flight side profile
@ -60,6 +62,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Stability vs. time
@ -73,6 +76,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Drag coefficients vs. Mach number
@ -97,6 +101,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Angle of attack and orientation vs. time
@ -110,6 +115,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
//// Simulation time step and computation time
@ -122,6 +128,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config);
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);

View File

@ -74,7 +74,7 @@ public final class MotorInstanceConfiguration implements Monitorable, Cloneable
}
public double getEjectionDelay(MotorId id) {
return ignitionDelays.get(indexOf(id));
return ejectionDelays.get(indexOf(id));
}
public MotorMount getMotorMount(MotorId id) {
@ -154,6 +154,7 @@ public final class MotorInstanceConfiguration implements Monitorable, Cloneable
clone.ids.addAll(this.ids);
clone.mounts.addAll(this.mounts);
clone.positions.addAll(this.positions);
clone.ejectionDelays.addAll(this.ejectionDelays);
clone.ignitionTimes.addAll(this.ignitionTimes);
clone.ignitionEvents.addAll(this.ignitionEvents);
clone.ignitionDelays.addAll(this.ignitionDelays);

View File

@ -38,6 +38,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// TODO: MEDIUM: Allow selecting steppers
private SimulationStepper flightStepper = new RK4SimulationStepper();
private SimulationStepper landingStepper = new BasicLandingStepper();
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
// Constant holding 30 degress in radians. This is the AOA condition
// necessary to transistion to tumbling.
private final static double AOA_TUMBLE_CONDITION = Math.PI / 3.0;
private SimulationStepper currentStepper;
@ -193,6 +198,24 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
// Check for Tumbling
// Conditions for transision are:
// apogee reached
// and is not already tumbling
// and not stable (cg > cp)
// and aoa > 30
if (status.isApogeeReached() && !status.isTumbling()) {
double cp = status.getFlightData().getLast(FlightDataType.TYPE_CP_LOCATION);
double cg = status.getFlightData().getLast(FlightDataType.TYPE_CG_LOCATION);
double aoa = status.getFlightData().getLast(FlightDataType.TYPE_AOA);
if (cg > cp && aoa > AOA_TUMBLE_CONDITION) {
addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, status.getSimulationTime()));
status.setTumbling(true);
}
}
}
} catch (SimulationException e) {
@ -474,6 +497,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
case ALTITUDE:
break;
case TUMBLE:
this.currentStepper = this.tumbleStepper;
this.status = currentStepper.initialize(status);
status.getFlightData().addEvent(event);
break;
}
}

View File

@ -0,0 +1,74 @@
package net.sf.openrocket.simulation;
import java.util.Iterator;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
public class BasicTumbleStatus extends SimulationStatus {
// Magic constants from techdoc.pdf
private final static double cDFin = 1.42;
private final static double cDBt = 0.56;
// Fin efficiency. Index is number of fins. The 0th entry is arbitrary and used to
// offset the indexes so finEff[1] is the coefficient for one fin from the table in techdoc.pdf
private final static double[] finEff = { 0.0, 0.5, 1.0, 1.41, 1.81, 1.73, 1.90, 1.85 };
private double drag;
public BasicTumbleStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions) {
super(configuration, motorConfiguration, simulationConditions);
computeTumbleDrag();
}
public BasicTumbleStatus(SimulationStatus orig) {
super(orig);
if (orig instanceof BasicTumbleStatus) {
this.drag = ((BasicTumbleStatus) orig).drag;
}
}
public double getTumbleDrag() {
return drag;
}
public void computeTumbleDrag() {
// Computed based on Sampo's experimentation as documented in the pdf.
// compute the fin and body tube projected areas
double aFins = 0.0;
double aBt = 0.0;
Rocket r = this.getConfiguration().getRocket();
Iterator<RocketComponent> componentIterator = r.iterator();
while (componentIterator.hasNext()) {
RocketComponent component = componentIterator.next();
if (!component.isAerodynamic()) {
continue;
}
if (component instanceof FinSet) {
double finComponent = ((FinSet) component).getFinArea();
int finCount = ((FinSet) component).getFinCount();
// check bounds on finCount.
if (finCount >= finEff.length) {
finCount = finEff.length - 1;
}
aFins += finComponent * finEff[finCount];
} else if (component instanceof SymmetricComponent) {
aBt += ((SymmetricComponent) component).getComponentPlanformArea();
}
}
drag = (cDFin * aFins + cDBt * aBt);
}
}

View File

@ -0,0 +1,137 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.GeodeticComputationStrategy;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.WorldCoordinate;
public class BasicTumbleStepper extends AbstractSimulationStepper {
private static final double RECOVERY_TIME_STEP = 0.5;
@Override
public SimulationStatus initialize(SimulationStatus status) throws SimulationException {
return new BasicTumbleStatus(status);
}
@Override
public void step(SimulationStatus status, double maxTimeStep) throws SimulationException {
// Get the atmospheric conditions
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
//// Local wind speed and direction
Coordinate windSpeed = modelWindVelocity(status);
Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
// Get total CD
double mach = airSpeed.length() / atmosphere.getMachSpeed();
double tumbleDrag = ((BasicTumbleStatus)status).getTumbleDrag();
// Compute drag force
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
double dragForce = tumbleDrag * dynP;
MassData massData = calculateMassData(status);
double mass = massData.getCG().weight;
// Compute drag acceleration
Coordinate linearAcceleration;
if (airSpeed.length() > 0.001) {
linearAcceleration = airSpeed.normalize().multiply(-dragForce / mass);
} else {
linearAcceleration = Coordinate.NUL;
}
// Add effect of gravity
double gravity = modelGravity(status);
linearAcceleration = linearAcceleration.sub(0, 0, gravity);
// Add coriolis acceleration
Coordinate coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration(
status.getRocketWorldPosition(), status.getRocketVelocity());
linearAcceleration = linearAcceleration.add(coriolisAcceleration);
// Select time step
double timeStep = MathUtil.min(0.5 / linearAcceleration.length(), RECOVERY_TIME_STEP);
// Perform Euler integration
status.setRocketPosition(status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2)));
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));
status.setSimulationTime(status.getSimulationTime() + timeStep);
// Update the world coordinate
WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
status.setRocketWorldPosition(w);
// Store data
FlightDataBranch data = status.getFlightData();
boolean extra = status.getSimulationConditions().isCalculateExtras();
data.addPoint();
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
if (extra) {
data.setValue(FlightDataType.TYPE_POSITION_XY,
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
MathUtil.hypot(linearAcceleration.x, linearAcceleration.y));
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length());
double Re = airSpeed.length() *
status.getConfiguration().getLength() /
atmosphere.getKinematicViscosity();
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
}
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
data.setValue(FlightDataType.TYPE_GRAVITY, gravity);
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length());
}
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z);
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, airSpeed.length());
data.setValue(FlightDataType.TYPE_MACH_NUMBER, mach);
data.setValue(FlightDataType.TYPE_MASS, mass);
data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, 0.0); // Is this a reasonable assumption? Probably.
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0);
data.setValue(FlightDataType.TYPE_DRAG_FORCE, dragForce);
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, windSpeed.length());
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
}
}

View File

@ -72,7 +72,12 @@ public class FlightEvent implements Comparable<FlightEvent> {
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
* which contains the old and new altitudes.
*/
ALTITUDE(trans.get("FlightEvent.Type.ALTITUDE"));
ALTITUDE(trans.get("FlightEvent.Type.ALTITUDE")),
/**
* The rocket begins to tumble.
*/
TUMBLE(trans.get("FlightEvent.Type.TUMBLE"));
private final String name;

View File

@ -69,6 +69,9 @@ public class SimulationStatus implements Monitorable {
/** Set to true when apogee has been detected. */
private boolean apogeeReached = false;
/** Set to true to indicate the rocket is tumbling. */
private boolean tumbling = false;
/** Contains a list of deployed recovery devices. */
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
@ -179,6 +182,7 @@ public class SimulationStatus implements Monitorable {
this.liftoff = orig.liftoff;
this.launchRodCleared = orig.launchRodCleared;
this.apogeeReached = orig.apogeeReached;
this.tumbling = orig.tumbling;
this.motorBurntOut = orig.motorBurntOut;
this.deployedRecoveryDevices.clear();
@ -380,6 +384,15 @@ public class SimulationStatus implements Monitorable {
}
public void setTumbling( boolean tumbling ) {
this.tumbling = tumbling;
this.modID++;
}
public boolean isTumbling() {
return tumbling;
}
public Set<RecoveryDevice> getDeployedRecoveryDevices() {
return deployedRecoveryDevices;
}