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
@ -21,7 +25,6 @@
* Modified the zoom and pan controls in the simulation plot. Added zoom in, out, and reset buttons. Added
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;
}