Merge branch 'kruland-integration' of github.com:plaa/openrocket into kruland-integration-ui
This commit is contained in:
commit
83fcf0894e
@ -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
|
2012-10-01 Kevin Ruland
|
||||||
* Extended "motor configuration" concept to cover more properties. The concept was renamed "Flight Configuration" and
|
* 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.
|
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
|
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.
|
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
|
* 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.
|
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
|
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
|
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).
|
contributed a more logical mouse controlled zoom - right click and drag will zoom (either domain, range or both).
|
||||||
|
|
||||||
|
|
||||||
2012-09-28 Sampo Niskane
|
2012-09-28 Sampo Niskane
|
||||||
* Released version 12.09.1
|
* Released version 12.09.1
|
||||||
|
|
||||||
|
|||||||
@ -43,3 +43,7 @@ The following file format versions exist:
|
|||||||
Added lowerstageseparation as recovery device deployment event.
|
Added lowerstageseparation as recovery device deployment event.
|
||||||
Added <datatypes> section for supporting datatypes other than
|
Added <datatypes> section for supporting datatypes other than
|
||||||
internal ones. Currently only supports datatypes from custom expressions.
|
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.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1383,6 +1383,7 @@ FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment
|
|||||||
FlightEvent.Type.GROUND_HIT = Ground hit
|
FlightEvent.Type.GROUND_HIT = Ground hit
|
||||||
FlightEvent.Type.SIMULATION_END = Simulation end
|
FlightEvent.Type.SIMULATION_END = Simulation end
|
||||||
FlightEvent.Type.ALTITUDE = Altitude change
|
FlightEvent.Type.ALTITUDE = Altitude change
|
||||||
|
FlightEvent.Type.TUMBLE = Tumbling
|
||||||
|
|
||||||
! ThrustCurveMotorColumns
|
! ThrustCurveMotorColumns
|
||||||
TCurveMotorCol.MANUFACTURER = Manufacturer
|
TCurveMotorCol.MANUFACTURER = Manufacturer
|
||||||
|
|||||||
@ -205,7 +205,7 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
|
private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
|
||||||
/*
|
/*
|
||||||
* File version 1.6 is required for:
|
* 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:
|
* File version 1.5 is requires for:
|
||||||
* - saving designs using ComponentPrests
|
* - saving designs using ComponentPrests
|
||||||
@ -223,11 +223,12 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
* Otherwise use version 1.0.
|
* 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()) {
|
for (RocketComponent c : document.getRocket()) {
|
||||||
if (c.getAppearance() != null) {
|
if (c.getAppearance() != null) {
|
||||||
return FILE_VERSION_DIVISOR + 6;
|
return FILE_VERSION_DIVISOR + 6;
|
||||||
}
|
}
|
||||||
|
// FIXME - test for flight configurations
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search the rocket for any ComponentPresets (version 1.5)
|
// 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)
|
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
double previousTime = -100000;
|
double previousTime = -100000;
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -73,7 +73,6 @@ import net.sf.openrocket.gui.dialogs.BugReportDialog;
|
|||||||
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
|
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.DebugLogDialog;
|
import net.sf.openrocket.gui.dialogs.DebugLogDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.DetailDialog;
|
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.LicenseDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.PrintDialog;
|
import net.sf.openrocket.gui.dialogs.PrintDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.ScaleDialog;
|
import net.sf.openrocket.gui.dialogs.ScaleDialog;
|
||||||
@ -429,34 +428,12 @@ public class BasicFrame extends JFrame {
|
|||||||
item.setIcon(Icons.FILE_OPEN);
|
item.setIcon(Icons.FILE_OPEN);
|
||||||
menu.add(item);
|
menu.add(item);
|
||||||
|
|
||||||
// FIXME - one of these two example menu items needs to be removed.
|
|
||||||
//// Open example...
|
//// Open example...
|
||||||
item = new ExampleDesignFileAction(trans.get("main.menu.file.openExample"), this);
|
item = new ExampleDesignFileAction(trans.get("main.menu.file.openExample"), this);
|
||||||
item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openexamplerocketdesign"));
|
item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openexamplerocketdesign"));
|
||||||
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
|
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
|
||||||
menu.add(item);
|
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();
|
menu.addSeparator();
|
||||||
|
|
||||||
//// Save
|
//// Save
|
||||||
|
|||||||
@ -36,6 +36,7 @@ public class PlotConfiguration implements Cloneable {
|
|||||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Total motion vs. time
|
//// 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.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Flight side profile
|
//// Flight side profile
|
||||||
@ -60,6 +62,7 @@ public class PlotConfiguration implements Cloneable {
|
|||||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Stability vs. time
|
//// Stability vs. time
|
||||||
@ -73,6 +76,7 @@ public class PlotConfiguration implements Cloneable {
|
|||||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Drag coefficients vs. Mach number
|
//// 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.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Angle of attack and orientation vs. time
|
//// 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.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
//// Simulation time step and computation time
|
//// 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.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||||
|
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||||
configs.add(config);
|
configs.add(config);
|
||||||
|
|
||||||
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
|
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
|
||||||
|
|||||||
@ -74,7 +74,7 @@ public final class MotorInstanceConfiguration implements Monitorable, Cloneable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getEjectionDelay(MotorId id) {
|
public double getEjectionDelay(MotorId id) {
|
||||||
return ignitionDelays.get(indexOf(id));
|
return ejectionDelays.get(indexOf(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MotorMount getMotorMount(MotorId id) {
|
public MotorMount getMotorMount(MotorId id) {
|
||||||
@ -154,6 +154,7 @@ public final class MotorInstanceConfiguration implements Monitorable, Cloneable
|
|||||||
clone.ids.addAll(this.ids);
|
clone.ids.addAll(this.ids);
|
||||||
clone.mounts.addAll(this.mounts);
|
clone.mounts.addAll(this.mounts);
|
||||||
clone.positions.addAll(this.positions);
|
clone.positions.addAll(this.positions);
|
||||||
|
clone.ejectionDelays.addAll(this.ejectionDelays);
|
||||||
clone.ignitionTimes.addAll(this.ignitionTimes);
|
clone.ignitionTimes.addAll(this.ignitionTimes);
|
||||||
clone.ignitionEvents.addAll(this.ignitionEvents);
|
clone.ignitionEvents.addAll(this.ignitionEvents);
|
||||||
clone.ignitionDelays.addAll(this.ignitionDelays);
|
clone.ignitionDelays.addAll(this.ignitionDelays);
|
||||||
|
|||||||
@ -38,6 +38,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
|||||||
// TODO: MEDIUM: Allow selecting steppers
|
// TODO: MEDIUM: Allow selecting steppers
|
||||||
private SimulationStepper flightStepper = new RK4SimulationStepper();
|
private SimulationStepper flightStepper = new RK4SimulationStepper();
|
||||||
private SimulationStepper landingStepper = new BasicLandingStepper();
|
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;
|
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) {
|
} catch (SimulationException e) {
|
||||||
@ -474,6 +497,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
|||||||
|
|
||||||
case ALTITUDE:
|
case ALTITUDE:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TUMBLE:
|
||||||
|
this.currentStepper = this.tumbleStepper;
|
||||||
|
this.status = currentStepper.initialize(status);
|
||||||
|
status.getFlightData().addEvent(event);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
74
core/src/net/sf/openrocket/simulation/BasicTumbleStatus.java
Normal file
74
core/src/net/sf/openrocket/simulation/BasicTumbleStatus.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java
Normal file
137
core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -72,7 +72,12 @@ public class FlightEvent implements Comparable<FlightEvent> {
|
|||||||
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
|
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
|
||||||
* which contains the old and new altitudes.
|
* 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;
|
private final String name;
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,9 @@ public class SimulationStatus implements Monitorable {
|
|||||||
/** Set to true when apogee has been detected. */
|
/** Set to true when apogee has been detected. */
|
||||||
private boolean apogeeReached = false;
|
private boolean apogeeReached = false;
|
||||||
|
|
||||||
|
/** Set to true to indicate the rocket is tumbling. */
|
||||||
|
private boolean tumbling = false;
|
||||||
|
|
||||||
/** Contains a list of deployed recovery devices. */
|
/** Contains a list of deployed recovery devices. */
|
||||||
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
|
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
|
||||||
|
|
||||||
@ -179,6 +182,7 @@ public class SimulationStatus implements Monitorable {
|
|||||||
this.liftoff = orig.liftoff;
|
this.liftoff = orig.liftoff;
|
||||||
this.launchRodCleared = orig.launchRodCleared;
|
this.launchRodCleared = orig.launchRodCleared;
|
||||||
this.apogeeReached = orig.apogeeReached;
|
this.apogeeReached = orig.apogeeReached;
|
||||||
|
this.tumbling = orig.tumbling;
|
||||||
this.motorBurntOut = orig.motorBurntOut;
|
this.motorBurntOut = orig.motorBurntOut;
|
||||||
|
|
||||||
this.deployedRecoveryDevices.clear();
|
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() {
|
public Set<RecoveryDevice> getDeployedRecoveryDevices() {
|
||||||
return deployedRecoveryDevices;
|
return deployedRecoveryDevices;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user