Initial GUI for editing and saving component presets. See ComponentPresetPanel.main() for standalone execution.

This commit is contained in:
Doug Pedrick 2012-05-05 02:23:14 +00:00
parent 0a199d73c3
commit 3d73e9d9eb
12 changed files with 2376 additions and 227 deletions

View File

@ -25,7 +25,7 @@ RocketActions.DelCompAct.Delete = Delete
RocketActions.DelCompAct.ttip.Delete = Delete the selected component.
RocketActions.DelSimuAct.Delete = Delete
RocketActions.DelSimuAct.ttip.Delete = Delete the selected simulation.
RocketActions.DelAct.Delete = Delete
RocketActions.DelAct.Delete = Delete
RocketActions.DelAct.ttip.Delete = Delete the selected component or simulation.
RocketActions.CutAction.Cut = Cut
RocketActions.CutAction.ttip.Cut = Cut this component or simulation to the clipboard and remove from this design
@ -47,7 +47,7 @@ RocketActions.MoveDownAct.ttip.Movedown = Move this component downwards.
RocketPanel.FigTypeAct.Sideview = Side view
RocketPanel.FigTypeAct.ttip.Sideview = Side view
RocketPanel.FigTypeAct.Backview = Back view
RocketPanel.FigTypeAct.ttip.Backview = Rear view
RocketPanel.FigTypeAct.ttip.Backview = Rear view
RocketPanel.FigViewAct.2D = 2D View
RocketPanel.FigViewAct.ttip.2D = 2D View
RocketPanel.FigViewAct.3D = 3D View
@ -95,6 +95,7 @@ filetypes.pdf = PDF files (*.pdf)
BasicFrame.SimpleFileFilter1 = All rocket designs (*.ork; *.rkt)
BasicFrame.SimpleFileFilter2 = OpenRocket designs (*.ork)
BasicFrame.SimpleFileFilter3 = RockSim designs (*.rkt)
BasicFrame.SimpleFileFilter4 = OpenRocket presets (*.orc)
filetypes.images = Image files
@ -106,9 +107,9 @@ AboutDialog.lbl.version = Version
! - AboutDialog.lbl.translatorWebsite is a URL to the translator / group (may be empty)
! - AboutDialog.lbl.translatorIcon is the file name of an icon under pix/translators/ (may be empty)
AboutDialog.lbl.translation = English translation by:
AboutDialog.lbl.translator =
AboutDialog.lbl.translatorWebsite =
AboutDialog.lbl.translatorIcon =
AboutDialog.lbl.translator =
AboutDialog.lbl.translatorWebsite =
AboutDialog.lbl.translatorIcon =
! Print dialog
@ -219,7 +220,7 @@ pref.dlg.but.reset = Reset
pref.dlg.but.checknow = Check now
pref.dlg.but.defaultmetric = Default metric
pref.dlg.but.defaultimperial = Default imperial
pref.dlg.title.Preferences = Preferences
pref.dlg.title.Preferences = Preferences
pref.dlg.tab.Units = Units
pref.dlg.tab.Defaultunits = Default units
pref.dlg.tab.Materials = Materials
@ -338,7 +339,7 @@ simedtdlg.lbl.ttip.Timestep1 = <html>The time between simulation steps.<br>A sma
simedtdlg.lbl.ttip.Timestep2 = The 4<sup>th</sup> order simulation method is quite accurate with a time step of
simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
simedtdlg.border.Simlist = Simulator listeners
simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
simedtdlg.txt.longA2 = For details on writing simulation listeners, see the OpenRocket technical documentation.
simedtdlg.lbl.Curlist = Current listeners:
simedtdlg.lbl.Addsimlist = Add simulation listener
@ -432,7 +433,7 @@ SimExpPan.Fileexists.desc1 = File \"
SimExpPan.Fileexists.desc2 = \" exists. Overwrite?
SimExpPan.Fileexists.title = File exists
SimExpPan.ExportingVar.desc1 = Exporting 1 variable out of
SimExpPan.ExportingVar.desc2 = Exporting
SimExpPan.ExportingVar.desc2 = Exporting
SimExpPan.ExportingVar.desc3 = variables out of
SimExpPan.Col.Variable = Variable
SimExpPan.Col.Unit = Unit
@ -470,7 +471,7 @@ simplotpanel.but.Plotflight = Plot flight
simplotpanel.lbl.Axis = Axis:
simplotpanel.but.ttip.Removethisplot = Remove this plot
simplotpanel.Desc = The data will be plotted in time order even if the X axis type is not time.
simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed.
simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed.
simplotpanel.OptionPane.lbl2 = Cannot add plot
simplotpanel.AUTO_NAME = Auto
simplotpanel.LEFT_NAME = Left
@ -536,14 +537,14 @@ componentanalysisdlg.rollTableModel = Roll dynamics
componentanalysisdlg.rollTableModel.ttip = Roll dynamics
componentanalysisdlg.println.closingmethod = Closing method called:
componentanalysisdlg.println.settingnam = SETTING NAN VALUES
componentanalysisdlg.lbl.reflenght = Reference length:
componentanalysisdlg.lbl.refarea = Reference area:
componentanalysisdlg.lbl.reflenght = Reference length:
componentanalysisdlg.lbl.refarea = Reference area:
!componentanalysisdlg.But.close =Close
componentanalysisdlg.TabStability.Col.Component = Component
! Custom Material dialog
custmatdlg.title.Custommaterial = Custom material
custmatdlg.lbl.Materialname = Material name:
custmatdlg.lbl.Materialname = Material name:
custmatdlg.lbl.Materialtype = Material type:
custmatdlg.lbl.Materialdensity = Material density:
custmatdlg.checkbox.Addmaterial = Add material to database
@ -656,7 +657,7 @@ RocketCompCfg.checkbox.Endcapped = End capped
RocketCompCfg.ttip.Endcapped = Whether the end of the shoulder is capped.
RocketCompCfg.title.Noseconeshoulder = Nose cone shoulder
RocketCompCfg.title.Aftshoulder = Aft shoulder
RocketCompCfg.border.Foreshoulder = Fore shoulder
RocketCompCfg.border.Foreshoulder = Fore shoulder
!RocketCompCfg.lbl.Length = Length:
! BulkheadConfig
@ -800,7 +801,7 @@ ParachuteCfg.lbl.Material = Material:
ParachuteCfg.combo.MaterialModel = The component material affects the weight of the component.
ParachuteCfg.lbl.longA1 = <html>Drag coefficient C<sub>D</sub>:
ParachuteCfg.lbl.longB1 = <html>The drag coefficient relative to the total area of the parachute.<br>
ParachuteCfg.lbl.longB2 = A larger drag coefficient yields a slowed descent rate.
ParachuteCfg.lbl.longB2 = A larger drag coefficient yields a slowed descent rate.
ParachuteCfg.lbl.longB3 = A typical value for parachutes is 0.8.
ParachuteCfg.but.Reset = Reset
ParachuteCfg.lbl.Shroudlines = Shroud lines:
@ -823,7 +824,7 @@ ParachuteCfg.lbl.Radialdirection = Radial direction:
ParachuteCfg.but.Reset = Reset
ParachuteCfg.lbl.plusdelay = plus
! ShockCordConfig
! ShockCordConfig
ShockCordCfg.lbl.Shockcordlength = Shock cord length:
ShockCordCfg.lbl.Shockcordmaterial = Shock cord material:
ShockCordCfg.lbl.Posrelativeto = Position relative to:
@ -967,7 +968,7 @@ PlotDialog.lbl.Chart = Click and drag down+right to zoom in, up+left to zoom out
! "main" prefix is used for the main application dialog
# FIXME: Rename the description keys
# FIXME: Rename the description keys
main.menu.file = File
main.menu.file.desc = File-handling related tasks
@ -1104,7 +1105,7 @@ Shape.Ogive.desc1 = An ogive nose cone has a profile that is a segment of a circ
Shape.Ogive.desc2 = An ogive transition has a profile that is a segment of a circle. The shape parameter value 1 produces a <b>tangent ogive</b>, which has a smooth transition to the body tube at the aft end, values less than 1 produce <b>secant ogives</b>.
Shape.Ellipsoid = Ellipsoid
Shape.Ellipsoid.desc1 = An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
Shape.Ellipsoid.desc2 = An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>. If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
Shape.Ellipsoid.desc2 = An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>. If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
Shape.Powerseries = Power series
Shape.Powerseries.desc1 = A power series nose cone has a profile of <i>Radius</i>&nbsp;&times;&nbsp;(<i>x</i>&nbsp;/&nbsp;<i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 this is a <b>\u00BD-power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.
Shape.Powerseries.desc2 = A power series transition has a profile of <i>Radius</i>&nbsp;&times;&nbsp;(<i>x</i>&nbsp;/&nbsp;<i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 the transition is <b>\u00BD-power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 <b>conical</b>.
@ -1113,7 +1114,7 @@ Shape.Parabolicseries.desc1 = A parabolic series nose cone has a profile of a pa
Shape.Parabolicseries.desc2 = A parabolic series transition has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
Shape.Haackseries = Haack series
Shape.Haackseries.desc1 = The Haack series nose cones are designed to minimize drag. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
Shape.Haackseries.desc2 = The Haack series <i>nose cones</i> are designed to minimize drag. These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
Shape.Haackseries.desc2 = The Haack series <i>nose cones</i> are designed to minimize drag. These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
! RocketComponent
@ -1169,7 +1170,7 @@ MotorMount.IgnitionEvent.EJECTION_CHARGE = First ejection charge of previous sta
MotorMount.IgnitionEvent.BURNOUT = First burnout of previous stage
MotorMount.IgnitionEvent.NEVER = Never
!ComponentIcons
!ComponentIcons
ComponentIcons.Nosecone = Nose cone
ComponentIcons.Bodytube = Body tube
ComponentIcons.Transition = Transition
@ -1224,7 +1225,7 @@ TCurveMotorCol.LENGTH = Length
! RocketInfo
RocketInfo.lengthLine.Length = Length
RocketInfo.lengthLine.maxdiameter = , max. diameter
RocketInfo.massText1 = Mass with motors
RocketInfo.massText1 = Mass with motors
RocketInfo.massText2 = Mass with no motors
RocketInfo.at = at M=
RocketInfo.cgText = CG:
@ -1577,8 +1578,8 @@ GuidedTourSelectionDialog.btn.start = Start tour!
! Custom Fin BMP Importer
CustomFinImport.button.label = Import from image
CustomFinImport.badFinImage = Invalid fin image. Make sure the fin is a solid black or dark color and touching the bottom of the image.
CustomFinImport.errorLoadingFile = Error loading file:
CustomFinImport.errorParsingFile = Error parsing fin image:
CustomFinImport.errorLoadingFile = Error loading file:
CustomFinImport.errorParsingFile = Error parsing fin image:
CustomFinImport.undo = Import freeform fin set
CustomFinImport.error.title = Error loading fin profile
CustomFinImport.error.badimage = Could not deduce fin shape from image.

View File

@ -0,0 +1,229 @@
package net.sf.openrocket.gui.preset;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.ResourceBundleTranslator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
import net.sf.openrocket.startup.Application;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.xml.bind.JAXBException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A UI for editing component presets. Currently this is a standalone application - run the main within this class.
* TODO: Full I18n
* TODO: Delete component
* TODO: Open .orc for editing
* TODO: Import .csv
* TODO: Export .csv
* TODO: proper mass unit conversion
*/
public class ComponentPresetPanel extends JPanel implements PresetResultListener {
private static final LogHelper log = Application.getLogger();
private static ResourceBundleTranslator trans = null;
static {
trans = new ResourceBundleTranslator("l10n.messages");
net.sf.openrocket.startup.Application.setBaseTranslator(trans);
}
private JTable table;
private DataTableModel model;
private boolean editingSelected = false;
/**
* Create the panel.
*/
public ComponentPresetPanel() {
setLayout(new MigLayout("", "[82.00px][168.00px][84px][117.00px][][222px]", "[346.00px][29px]"));
model = new DataTableModel(new String[]{"Manufacturer", "Type", "Part No", "Description"});
table = new JTable(model);
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
add(scrollPane, "cell 0 0 6 1,grow");
table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
JTable target = (JTable) e.getSource();
int row = target.getSelectedRow();
editingSelected = true;
new PresetEditorDialog(ComponentPresetPanel.this, (ComponentPreset) model.getAssociatedObject(row)).setVisible(true);
}
}
});
JButton addBtn = new JButton("Add");
addBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
editingSelected = false;
new PresetEditorDialog(ComponentPresetPanel.this).setVisible(true);
}
});
add(addBtn, "cell 0 1,alignx left,aligny top");
JButton saveBtn = new JButton("Save...");
saveBtn.setHorizontalAlignment(SwingConstants.RIGHT);
add(saveBtn, "flowx,cell 5 1,alignx right,aligny center");
saveBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
try {
saveAsORC();
}
catch (JAXBException e1) {
//TODO
e1.printStackTrace();
}
catch (IOException e1) {
//TODO
e1.printStackTrace();
}
}
});
JButton cancelBtn = new JButton("Cancel");
cancelBtn.setHorizontalAlignment(SwingConstants.RIGHT);
add(cancelBtn, "cell 5 1,alignx right,aligny top");
cancelBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
System.exit(0);
}
});
}
@Override
public void notifyResult(final ComponentPreset preset) {
if (preset != null) {
DataTableModel model = (DataTableModel) table.getModel();
if (!editingSelected) {
model.addRow(new String[]{preset.getManufacturer().getDisplayName(), preset.getType().name(), preset.getPartNo(), preset.get(ComponentPreset.DESCRIPTION)},
preset);
}
else {
int row = table.getSelectedRow();
model.setValueAt(preset.getManufacturer().getDisplayName(), row, 0);
model.setValueAt(preset.getType().name(), row, 1);
model.setValueAt(preset.getPartNo(), row, 2);
model.setValueAt(preset.get(ComponentPreset.DESCRIPTION), row, 3);
model.associated.set(row, preset);
}
}
editingSelected = false;
}
/**
* Launch the test main.
*/
public static void main(String[] args) {
try {
Application.setPreferences(new SwingPreferences());
JFrame dialog = new JFrame();
dialog.getContentPane().add(new ComponentPresetPanel());
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.pack();
dialog.setVisible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
class DataTableModel extends DefaultTableModel {
private List<Object> associated = new ArrayList<Object>();
/**
* Constructs a <code>DefaultTableModel</code> with as many columns as there are elements in
* <code>columnNames</code> and <code>rowCount</code> of <code>null</code> object values. Each column's name
* will be taken from the <code>columnNames</code> array.
*
* @param columnNames <code>array</code> containing the names of the new columns; if this is <code>null</code>
* then the model has no columns
*
* @see #setDataVector
* @see #setValueAt
*/
DataTableModel(final Object[] columnNames) {
super(columnNames, 0);
}
public void addRow(Object[] data, Object associatedData) {
super.addRow(data);
associated.add(getRowCount() - 1, associatedData);
}
public void removeRow(int row) {
super.removeRow(row);
associated.remove(row);
}
public Object getAssociatedObject(int row) {
return associated.get(row);
}
public boolean isCellEditable(int rowIndex, int mColIndex) {
return false;
}
}
private boolean saveAsORC() throws JAXBException, IOException {
File file = null;
final JFileChooser chooser = new JFileChooser();
chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
int option = chooser.showSaveDialog(ComponentPresetPanel.this);
if (option != JFileChooser.APPROVE_OPTION) {
log.user("User decided not to save, option=" + option);
return false;
}
file = chooser.getSelectedFile();
if (file == null) {
log.user("User did not select a file");
return false;
}
((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
file = FileHelper.forceExtension(file, "orc");
List<Material> materials = new ArrayList<Material>();
List<ComponentPreset> presets = new ArrayList<ComponentPreset>();
for (int x = 0; x< model.getRowCount(); x++) {
ComponentPreset preset = (ComponentPreset)model.getAssociatedObject(x);
if (!materials.contains(preset.get(ComponentPreset.MATERIAL))) {
materials.add(preset.get(ComponentPreset.MATERIAL));
}
presets.add(preset);
}
return FileHelper.confirmWrite(file, this) && new OpenRocketComponentSaver().save(file, materials, presets);
}
}

View File

@ -0,0 +1,105 @@
package net.sf.openrocket.gui.preset;
import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
/**
* From a JavaLobby article by Michael Urban: http://www.javalobby.org/java/forums/t49462.html
*/
public class ImagePreviewPanel extends JPanel
implements PropertyChangeListener {
private int width, height;
private ImageIcon icon;
private Image image;
private static final int ACCSIZE = 155;
private Color bg;
public ImagePreviewPanel() {
setPreferredSize(new Dimension(ACCSIZE, -1));
bg = getBackground();
}
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
// Make sure we are responding to the right event.
if (propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
File selection = (File)e.getNewValue();
String name;
if (selection == null)
return;
else
name = selection.getAbsolutePath();
/*
* Make reasonably sure we have an image format that AWT can
* handle so we don't try to draw something silly.
*/
if ((name != null) &&
name.toLowerCase().endsWith(".jpg") ||
name.toLowerCase().endsWith(".jpeg") ||
name.toLowerCase().endsWith(".gif") ||
name.toLowerCase().endsWith(".png")) {
icon = new ImageIcon(name);
image = icon.getImage();
scaleImage();
repaint();
}
}
}
private void scaleImage() {
width = image.getWidth(this);
height = image.getHeight(this);
double ratio = 1.0;
/*
* Determine how to scale the image. Since the accessory can expand
* vertically make sure we don't go larger than 150 when scaling
* vertically.
*/
if (width >= height) {
ratio = (double)(ACCSIZE-5) / width;
width = ACCSIZE-5;
height = (int)(height * ratio);
}
else {
if (getHeight() > 150) {
ratio = (double)(ACCSIZE-5) / height;
height = ACCSIZE-5;
width = (int)(width * ratio);
}
else {
ratio = (double)getHeight() / height;
height = getHeight();
width = (int)(width * ratio);
}
}
image = image.getScaledInstance(width, height, Image.SCALE_DEFAULT);
}
public void paintComponent(Graphics g) {
g.setColor(bg);
/*
* If we don't do this, we will end up with garbage from previous
* images if they have larger sizes than the one we are currently
* drawing. Also, it seems that the file list can paint outside
* of its rectangle, and will cause odd behavior if we don't clear
* or fill the rectangle for the accessory before drawing. This might
* be a bug in JFileChooser.
*/
g.fillRect(0, 0, ACCSIZE, getHeight());
g.drawImage(image, getWidth() / 2 - width / 2 + 5,
getHeight() / 2 - height / 2, this);
}
}

View File

@ -0,0 +1,120 @@
package net.sf.openrocket.gui.preset;
import net.sf.openrocket.database.Database;
import net.sf.openrocket.database.DatabaseListener;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.gui.dialogs.CustomMaterialDialog;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.startup.Application;
import javax.swing.*;
import java.awt.*;
/**
* A material model specifically for presets.
*/
public class MaterialModel extends DefaultComboBoxModel implements DatabaseListener<Material> {
private static final String CUSTOM = "Custom";
private final Database<Material> database;
private static final Translator trans = Application.getTranslator();
private Component parent;
public MaterialModel(Component theParent, Material.Type type) {
parent = theParent;
switch (type) {
case LINE:
this.database = Databases.LINE_MATERIAL;
break;
case BULK:
this.database = Databases.BULK_MATERIAL;
break;
case SURFACE:
this.database = Databases.SURFACE_MATERIAL;
break;
default:
throw new IllegalArgumentException("Unknown material type:" + type);
}
database.addDatabaseListener(this);
}
@Override
public void setSelectedItem(Object item) {
if (item == null) {
// Clear selection - huh?
return;
}
if (item == CUSTOM) {
// Open custom material dialog in the future, after combo box has closed
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CustomMaterialDialog dialog = new CustomMaterialDialog(
SwingUtilities.getWindowAncestor(parent),
(Material) getSelectedItem(), true,
//// Define custom material
trans.get("MaterialModel.title.Defcustmat"));
dialog.setVisible(true);
if (!dialog.getOkClicked()) {
return;
}
Material material = dialog.getMaterial();
MaterialModel.super.setSelectedItem(material);
if (dialog.isAddSelected()) {
database.add(material);
}
}
});
}
else if (item instanceof Material) {
super.setSelectedItem(item);
}
else {
throw new IllegalArgumentException("Illegal item class " + item.getClass() +
" item=" + item);
}
}
@Override
public Object getElementAt(int index) {
if (index == database.size()) {
return CUSTOM;
}
else if (index >= database.size() + 1) {
return null;
}
return database.get(index);
}
@Override
public int getSize() {
return database.size() + 1;
}
//////// Change listeners
@Override
public void elementAdded(Material element, Database<Material> source) {
this.fireContentsChanged(this, 0, database.size());
}
@Override
public void elementRemoved(Material element, Database<Material> source) {
this.fireContentsChanged(this, 0, database.size());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
package net.sf.openrocket.gui.preset;
import net.sf.openrocket.preset.ComponentPreset;
/**
*/
public interface PresetResultListener {
void notifyResult(ComponentPreset preset);
}

View File

@ -7,6 +7,14 @@ package net.sf.openrocket.gui.print;
* Utilities for print units.
*/
public enum PrintUnit {
FOOT {
public double toInches(double d) { return d*12; }
public double toMillis(double d) { return d/FEET_PER_MM; }
public double toCentis(double d) { return d/(FEET_PER_MM*TEN); }
public double toMeters(double d) { return d/(FEET_PER_MM*TEN*TEN*TEN); }
public long toPoints(double d) { return (long)(d * POINTS_PER_INCH * 12); }
public double convert(double d, PrintUnit u) { return u.toInches(d)/12; }
},
INCHES {
public double toInches(double d) { return d; }
public double toMillis(double d) { return d/INCHES_PER_MM; }
@ -50,13 +58,14 @@ public enum PrintUnit {
// Handy constants for conversion methods
public static final double INCHES_PER_MM = 0.0393700787d;
public static final double FEET_PER_MM = INCHES_PER_MM /12;
public static final double MM_PER_INCH = 1.0d/INCHES_PER_MM;
public static final long TEN = 10;
/**
* PPI is Postscript Point and is a standard of 72. Java2D also uses this internally as a pixel-per-inch, so pixels
* and points are for the most part interchangeable (unless the defaults are changed), which makes translating
* between the screen and a print job easier.
*
*
* Not to be confused with Dots-Per-Inch, which is printer and print mode dependent.
*/
public static final int POINTS_PER_INCH = 72;

View File

@ -1,70 +1,73 @@
package net.sf.openrocket.gui.util;
import java.awt.Component;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
/**
* Helper methods related to user-initiated file manipulation.
* <p>
* These methods log the necessary information to the debug log.
*
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public final class FileHelper {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
// TODO: HIGH: Rename translation keys
/** File filter for any rocket designs (*.ork, *.rkt) */
public static final FileFilter ALL_DESIGNS_FILTER =
new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter1"),
".ork", ".ork.gz", ".rkt", ".rkt.gz");
/** File filter for OpenRocket designs (*.ork) */
public static final FileFilter OPENROCKET_DESIGN_FILTER =
new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter2"), ".ork", ".ork.gz");
/** File filter for RockSim designs (*.rkt) */
public static final FileFilter ROCKSIM_DESIGN_FILTER =
new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter3"), ".rkt", ".rkt.gz");
/** File filter for OpenRocket components and presets (*.orc) */
public static final FileFilter OPEN_ROCKET_COMPONENT_FILTER =
new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter4"), ".orc", ".orc.gz");
/** File filter for PDF files (*.pdf) */
public static final FileFilter PDF_FILTER =
new SimpleFileFilter(trans.get("filetypes.pdf"), ".pdf");
/** File filter for CSV files (*.csv) */
public static final FileFilter CSV_FILE_FILTER =
new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv");
private FileHelper() {
// Prevent instantiation
}
public static FileFilter getImageFileFilter() {
String[] extensions = ImageIO.getReaderFileSuffixes();
for (int i = 0; i < extensions.length; i++) {
extensions[i] = extensions[i].toLowerCase(Locale.ENGLISH);
}
Arrays.sort(extensions);
StringBuilder sb = new StringBuilder();
sb.append(trans.get("filetypes.images"));
sb.append(" (");
@ -75,31 +78,31 @@ public final class FileHelper {
}
}
sb.append(")");
return new SimpleFileFilter(sb.toString(), extensions);
}
/**
* Ensure that the provided file has a file extension. If the file does not have
* any extension, append the provided extension to it.
*
*
* @param original the original file
* @param extension the extension to append if none exists (without preceding dot)
* @return the resulting file
*/
public static File ensureExtension(File original, String extension) {
if (original.getName().indexOf('.') < 0) {
log.debug(1, "File name does not contain extension, adding '" + extension + "'");
String name = original.getAbsolutePath();
name = name + "." + extension;
return new File(name);
}
return original;
}
/**
* Ensure that the provided file has the given file extension. This differs from ensureExtension in that this
* method guarantees that the file will have the extension, whereas ensureExtension only treats the extension
@ -110,7 +113,7 @@ public final class FileHelper {
* @return the resulting file
*/
public static File forceExtension(File original, String extension) {
if (!original.getName().toLowerCase(Locale.ENGLISH).endsWith(extension.toLowerCase(Locale.ENGLISH))) {
log.debug(1, "File name does not contain extension, adding '" + extension + "'");
String name = original.getAbsolutePath();
@ -122,15 +125,15 @@ public final class FileHelper {
}
return new File(name);
}
return original;
}
/**
* Confirm that it is allowed to write to a file. If the file exists,
* a confirmation dialog will be presented to the user to ensure overwriting is ok.
*
*
* @param file the file that is going to be written.
* @param parent the parent component for the dialog.
* @return <code>true</code> to write, <code>false</code> to abort.
@ -149,23 +152,23 @@ public final class FileHelper {
}
return true;
}
/**
* Display an error message to the user that writing a file failed.
*
*
* @param e the I/O exception that caused the error.
* @param parent the parent component for the dialog.
*/
public static void errorWriting(IOException e, Component parent) {
log.warn(1, "Error writing to file", e);
JOptionPane.showMessageDialog(parent,
new Object[] {
trans.get("error.writing.desc"),
e.getLocalizedMessage()
}, trans.get("error.writing.title"), JOptionPane.ERROR_MESSAGE);
}
}

View File

@ -52,6 +52,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
ComponentPreset.SHAPE,
ComponentPreset.AFT_OUTER_DIAMETER,
ComponentPreset.AFT_SHOULDER_DIAMETER,
ComponentPreset.AFT_SHOULDER_LENGTH,
ComponentPreset.LENGTH} ),
TRANSITION( new TypedKey<?>[] {
@ -97,7 +98,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
ComponentPreset.INNER_DIAMETER,
ComponentPreset.OUTER_DIAMETER,
ComponentPreset.LENGTH} ),
LAUNCH_LUG( new TypedKey<?>[] {
ComponentPreset.MANUFACTURER,
ComponentPreset.PARTNO,
@ -114,7 +115,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
ComponentPreset.WIDTH,
ComponentPreset.THICKNESS,
ComponentPreset.MATERIAL} ),
PARACHUTE( new TypedKey<?>[] {
ComponentPreset.MANUFACTURER,
ComponentPreset.PARTNO,
@ -177,7 +178,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
public final static TypedKey<Integer> LINE_COUNT = new TypedKey<Integer>("LineCount", Integer.class);
public final static TypedKey<Double> LINE_LENGTH = new TypedKey<Double>("LineLength", Double.class, UnitGroup.UNITS_LENGTH);
public final static TypedKey<Material> LINE_MATERIAL = new TypedKey<Material>("LineMaterial", Material.class);
public final static TypedKey<byte[]> IMAGE = new TypedKey<byte[]>("Image", byte[].class);
public final static List<TypedKey<?>> orderedKeyList = Arrays.<TypedKey<?>>asList(
MANUFACTURER,

View File

@ -1,7 +1,6 @@
package net.sf.openrocket.preset.xml;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.motor.Manufacturer;
import net.sf.openrocket.preset.ComponentPreset;
@ -76,6 +75,9 @@ public abstract class BaseComponentDTO {
if ( preset.has(ComponentPreset.FILLED) ) {
setFilled( preset.get(ComponentPreset.FILLED));
}
if (preset.has(ComponentPreset.IMAGE) ) {
setImageData(preset.get(ComponentPreset.IMAGE));
}
}
public String getManufacturer() {
@ -171,6 +173,9 @@ public abstract class BaseComponentDTO {
if ( filled != null ) {
props.put(ComponentPreset.FILLED, getFilled());
}
if (image != null) {
props.put(ComponentPreset.IMAGE, image);
}
}
protected Material find(List<MaterialDTO> materialList, AnnotatedMaterialDTO dto) {
@ -195,7 +200,7 @@ public abstract class BaseComponentDTO {
} else {
return null;
}
}
static class AnnotatedMaterialDTO {

View File

@ -1,6 +1,17 @@
package net.sf.openrocket.preset.xml;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.InvalidComponentPresetException;
import net.sf.openrocket.startup.Application;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@ -10,16 +21,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.InvalidComponentPresetException;
import net.sf.openrocket.startup.Application;
/**
* The active manager class that is the entry point for reading and writing *.orc files.
*/
@ -39,6 +40,16 @@ public class OpenRocketComponentSaver {
}
}
public boolean save(File file, List<Material> theMaterialList, List<ComponentPreset> thePresetList) throws
JAXBException,
IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
writer.write(marshalToOpenRocketComponent(theMaterialList, thePresetList));
writer.flush();
writer.close();
return true;
}
/**
* This method marshals a list of materials and ComponentPresets into an .orc formatted XML string.
*
@ -57,31 +68,31 @@ public class OpenRocketComponentSaver {
StringWriter sw = new StringWriter();
// We're going to sort the initial data since that makes the output much easier on the eyes.
Collections.sort(theMaterialList, new Comparator<Material>() {
@Override
public int compare(Material o1, Material o2) {
return o1.getName().compareTo( o2.getName() );
}
});
Collections.sort(thePresetList, new Comparator<ComponentPreset>() {
@Override
public int compare(ComponentPreset o1, ComponentPreset o2) {
int manucmp = o1.getManufacturer().getSimpleName().compareTo( o2.getManufacturer().getSimpleName() );
if ( manucmp != 0 ) {
return manucmp;
}
return o1.getPartNo().compareTo( o2.getPartNo());
}
});
marshaller.marshal(toOpenRocketComponentDTO(theMaterialList, thePresetList), sw);
return sw.toString();
@ -90,8 +101,8 @@ public class OpenRocketComponentSaver {
/**
* This method unmarshals from a Reader that is presumed to be open on an XML file in .orc format.
*
* @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle UTF
* characters correctly
* @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle
* UTF characters correctly
*
* @return a list of ComponentPresets
*
@ -127,13 +138,13 @@ public class OpenRocketComponentSaver {
*
* @param is an open Reader; assumed to be opened on a file of XML in .orc format
*
* @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read or
* was in an invalid format
* @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read
* or was in an invalid format
*/
private OpenRocketComponentDTO fromOpenRocketComponent(Reader is) throws JAXBException {
/** The context is thread-safe, but unmarshallers are not. Create a local one. */
Unmarshaller unmarshaller = context.createUnmarshaller();
return (OpenRocketComponentDTO) unmarshaller.unmarshal(is);
return (OpenRocketComponentDTO) unmarshaller.unmarshal(is); //new StreamSource(is));
}
/**

View File

@ -1,10 +1,5 @@
package net.sf.openrocket.rocketcomponent;
import static java.lang.Math.sin;
import static net.sf.openrocket.util.MathUtil.*;
import java.util.Collection;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
@ -12,19 +7,25 @@ import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import java.util.Collection;
import static java.lang.Math.sin;
import static net.sf.openrocket.util.MathUtil.pow2;
import static net.sf.openrocket.util.MathUtil.pow3;
public class Transition extends SymmetricComponent {
private static final Translator trans = Application.getTranslator();
private static final double CLIP_PRECISION = 0.0001;
private Shape type;
private double shapeParameter;
private boolean clipped; // Not to be read - use isClipped(), which may be overriden
private double radius1, radius2;
private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic
private double foreShoulderRadius;
private double foreShoulderThickness;
@ -34,25 +35,25 @@ public class Transition extends SymmetricComponent {
private double aftShoulderThickness;
private double aftShoulderLength;
private boolean aftShoulderCapped;
// Used to cache the clip length
private double clipLength = -1;
public Transition() {
super();
this.radius1 = DEFAULT_RADIUS;
this.radius2 = DEFAULT_RADIUS;
this.length = DEFAULT_RADIUS * 3;
this.autoRadius1 = true;
this.autoRadius2 = true;
this.type = Shape.CONICAL;
this.shapeParameter = 0;
this.clipped = true;
}
//////// Length ////////
@Override
public void setLength( double length ) {
@ -66,7 +67,7 @@ public class Transition extends SymmetricComponent {
//////// Fore radius ////////
@Override
public double getForeRadius() {
@ -83,39 +84,39 @@ public class Transition extends SymmetricComponent {
}
return radius1;
}
public void setForeRadius(double radius) {
if ((this.radius1 == radius) && (autoRadius1 == false))
return;
this.autoRadius1 = false;
this.radius1 = Math.max(radius, 0);
if (this.thickness > this.radius1 && this.thickness > this.radius2)
this.thickness = Math.max(this.radius1, this.radius2);
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public boolean isForeRadiusAutomatic() {
return autoRadius1;
}
public void setForeRadiusAutomatic(boolean auto) {
if (autoRadius1 == auto)
return;
autoRadius1 = auto;
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
//////// Aft radius /////////
@Override
public double getAftRadius() {
if (isAftRadiusAutomatic()) {
@ -131,66 +132,66 @@ public class Transition extends SymmetricComponent {
}
return radius2;
}
public void setAftRadius(double radius) {
if ((this.radius2 == radius) && (autoRadius2 == false))
return;
this.autoRadius2 = false;
this.radius2 = Math.max(radius, 0);
if (this.thickness > this.radius1 && this.thickness > this.radius2)
this.thickness = Math.max(this.radius1, this.radius2);
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public boolean isAftRadiusAutomatic() {
return autoRadius2;
}
public void setAftRadiusAutomatic(boolean auto) {
if (autoRadius2 == auto)
return;
autoRadius2 = auto;
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
//// Radius automatics
@Override
protected double getFrontAutoRadius() {
if (isAftRadiusAutomatic())
return -1;
return getAftRadius();
}
@Override
protected double getRearAutoRadius() {
if (isForeRadiusAutomatic())
return -1;
return getForeRadius();
}
//////// Type & shape /////////
public Shape getType() {
return type;
}
public void setType(Shape type) {
if (type == null) {
throw new IllegalArgumentException("setType called with null argument");
@ -202,50 +203,50 @@ public class Transition extends SymmetricComponent {
this.shapeParameter = type.defaultParameter();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public double getShapeParameter() {
return shapeParameter;
}
public void setShapeParameter(double n) {
if (shapeParameter == n)
return;
this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter());
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public boolean isClipped() {
if (!type.isClippable())
return false;
return clipped;
}
public void setClipped(boolean c) {
if (clipped == c)
return;
clipped = c;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public boolean isClippedEnabled() {
return type.isClippable();
}
public double getShapeParameterMin() {
return type.minParameter();
}
public double getShapeParameterMax() {
return type.maxParameter();
}
//////// Shoulders ////////
public double getForeShoulderRadius() {
return foreShoulderRadius;
}
public void setForeShoulderRadius(double foreShoulderRadius) {
if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
return;
@ -253,47 +254,47 @@ public class Transition extends SymmetricComponent {
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public double getForeShoulderThickness() {
return foreShoulderThickness;
}
public void setForeShoulderThickness(double foreShoulderThickness) {
if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness))
return;
this.foreShoulderThickness = foreShoulderThickness;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public double getForeShoulderLength() {
return foreShoulderLength;
}
public void setForeShoulderLength(double foreShoulderLength) {
if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength))
return;
this.foreShoulderLength = foreShoulderLength;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public boolean isForeShoulderCapped() {
return foreShoulderCapped;
}
public void setForeShoulderCapped(boolean capped) {
if (this.foreShoulderCapped == capped)
return;
this.foreShoulderCapped = capped;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public double getAftShoulderRadius() {
return aftShoulderRadius;
}
public void setAftShoulderRadius(double aftShoulderRadius) {
if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
return;
@ -301,45 +302,45 @@ public class Transition extends SymmetricComponent {
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public double getAftShoulderThickness() {
return aftShoulderThickness;
}
public void setAftShoulderThickness(double aftShoulderThickness) {
if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness))
return;
this.aftShoulderThickness = aftShoulderThickness;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public double getAftShoulderLength() {
return aftShoulderLength;
}
public void setAftShoulderLength(double aftShoulderLength) {
if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength))
return;
this.aftShoulderLength = aftShoulderLength;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
public boolean isAftShoulderCapped() {
return aftShoulderCapped;
}
public void setAftShoulderCapped(boolean capped) {
if (this.aftShoulderCapped == capped)
return;
this.aftShoulderCapped = capped;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
/////////// Shape implementations ////////////
/**
@ -349,20 +350,20 @@ public class Transition extends SymmetricComponent {
public double getRadius(double x) {
if (x < 0 || x > length)
return 0;
double r1 = getForeRadius();
double r2 = getAftRadius();
if (r1 == r2)
return r1;
if (r1 > r2) {
x = length - x;
double tmp = r1;
r1 = r2;
r2 = tmp;
}
if (isClipped()) {
// Check clip calculation
if (clipLength < 0)
@ -373,7 +374,7 @@ public class Transition extends SymmetricComponent {
return r1 + type.getRadius(x, r2 - r1, length, shapeParameter);
}
}
/**
* Numerically solve clipLength from the equation
* r1 == type.getRadius(clipLength,r2,clipLength+length)
@ -381,27 +382,27 @@ public class Transition extends SymmetricComponent {
*/
private void calculateClip(double r1, double r2) {
double min = 0, max = length;
if (r1 >= r2) {
double tmp = r1;
r1 = r2;
r2 = tmp;
}
if (r1 == 0) {
clipLength = 0;
return;
}
if (length <= 0) {
clipLength = 0;
return;
}
// Required:
// getR(min,min+length,r2) - r1 < 0
// getR(max,max+length,r2) - r1 > 0
int n = 0;
while (type.getRadius(max, r2, max + length, shapeParameter) - r1 < 0) {
min = max;
@ -410,7 +411,7 @@ public class Transition extends SymmetricComponent {
if (n > 10)
break;
}
while (true) {
clipLength = (min + max) / 2;
if ((max - min) < CLIP_PRECISION)
@ -423,14 +424,14 @@ public class Transition extends SymmetricComponent {
}
}
}
@Override
public double getInnerRadius(double x) {
return Math.max(getRadius(x) - thickness, 0);
}
@Override
public Collection<Coordinate> getComponentBounds() {
@ -454,7 +455,7 @@ public class Transition extends SymmetricComponent {
final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
volume += ringVolume(ir, 0, getForeShoulderThickness() );
}
if (getAftShoulderLength() > 0.001) {
final double or = getAftShoulderRadius();
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
@ -464,7 +465,7 @@ public class Transition extends SymmetricComponent {
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
volume += ringVolume(ir, 0, getAftShoulderThickness() );
}
return volume;
}
@ -482,7 +483,7 @@ public class Transition extends SymmetricComponent {
getForeShoulderThickness() - getForeShoulderLength(),
getMaterial().getDensity()));
}
if (getAftShoulderLength() > 0.001) {
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(),
@ -496,8 +497,8 @@ public class Transition extends SymmetricComponent {
}
return cg;
}
/*
* The moments of inertia are not explicitly corrected for the shoulders.
* However, since the mass is corrected, the inertia is automatically corrected
@ -514,13 +515,13 @@ public class Transition extends SymmetricComponent {
//// Transition
return trans.get("Transition.Transition");
}
@Override
protected void componentChanged(ComponentChangeEvent e) {
super.componentChanged(e);
clipLength = -1;
}
/**
* Check whether the given type can be added to this component. Transitions allow any
* InternalComponents to be added.
@ -534,7 +535,7 @@ public class Transition extends SymmetricComponent {
return true;
return false;
}
@Override
public Type getPresetType() {
return ComponentPreset.Type.TRANSITION;
@ -548,7 +549,7 @@ public class Transition extends SymmetricComponent {
if ( preset.has(ComponentPreset.FILLED ) ) {
presetFilled = preset.get( ComponentPreset.FILLED);
}
if ( preset.has(ComponentPreset.SHAPE) ) {
Shape s = preset.get(ComponentPreset.SHAPE);
this.setType(s);
@ -598,7 +599,7 @@ public class Transition extends SymmetricComponent {
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public static enum Shape {
/**
* Conical shape.
*/
@ -616,7 +617,7 @@ public class Transition extends SymmetricComponent {
return radius * x / length;
}
},
/**
* Ogive shape. The shape parameter is the portion of an extended tangent ogive
* that will be used. That is, for param==1 a tangent ogive will be produced, and
@ -632,12 +633,12 @@ public class Transition extends SymmetricComponent {
public boolean usesParameter() {
return true; // Range 0...1 is default
}
@Override
public double defaultParameter() {
return 1.0; // Tangent ogive by default
}
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
@ -645,17 +646,17 @@ public class Transition extends SymmetricComponent {
assert radius >= 0;
assert param >= 0;
assert param <= 1;
// Impossible to calculate ogive for length < radius, scale instead
// TODO: LOW: secant ogive could be calculated lower
if (length < radius) {
x = x * radius / length;
length = radius;
}
if (param < 0.001)
return CONICAL.getRadius(x, radius, length, param);
// Radius of circle is:
double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
(pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
@ -665,7 +666,7 @@ public class Transition extends SymmetricComponent {
return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
}
},
/**
* Ellipsoidal shape.
*/
@ -673,7 +674,7 @@ public class Transition extends SymmetricComponent {
ELLIPSOID(trans.get("Shape.Ellipsoid"),
//// An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
trans.get("Shape.Ellipsoid.desc1"),
//// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>. If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
//// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>. If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
trans.get("Shape.Ellipsoid.desc2"), true) {
@Override
public double getRadius(double x, double radius, double length, double param) {
@ -684,7 +685,7 @@ public class Transition extends SymmetricComponent {
return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
}
},
//// Power series
POWER(trans.get("Shape.Powerseries"),
trans.get("Shape.Powerseries.desc1"),
@ -693,12 +694,12 @@ public class Transition extends SymmetricComponent {
public boolean usesParameter() { // Range 0...1
return true;
}
@Override
public double defaultParameter() {
return 0.5;
}
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
@ -714,29 +715,29 @@ public class Transition extends SymmetricComponent {
}
return radius * Math.pow(x / length, param);
}
},
//// Parabolic series
PARABOLIC(trans.get("Shape.Parabolicseries"),
////A parabolic series nose cone has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
trans.get("Shape.Parabolicseries.desc1"),
////A parabolic series transition has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
trans.get("Shape.Parabolicseries.desc2")) {
// In principle a parabolic transition is clippable, but the difference is
// negligible.
@Override
public boolean usesParameter() { // Range 0...1
return true;
}
@Override
public double defaultParameter() {
return 1.0;
}
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
@ -744,28 +745,28 @@ public class Transition extends SymmetricComponent {
assert radius >= 0;
assert param >= 0;
assert param <= 1;
return radius * ((2 * x / length - param * pow2(x / length)) / (2 - param));
}
},
//// Haack series
HAACK(trans.get("Shape.Haackseries"),
//// The Haack series nose cones are designed to minimize drag. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
trans.get("Shape.Haackseries.desc1"),
//// The Haack series <i>nose cones</i> are designed to minimize drag. These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
//// The Haack series <i>nose cones</i> are designed to minimize drag. These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
trans.get("Shape.Haackseries.desc2"), true) {
@Override
public boolean usesParameter() {
return true;
}
@Override
public double maxParameter() {
return 1.0 / 3.0; // Range 0...1/3
}
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
@ -773,7 +774,7 @@ public class Transition extends SymmetricComponent {
assert radius >= 0;
assert param >= 0;
assert param <= 2;
double theta = Math.acos(1 - 2 * x / length);
if (MathUtil.equals(param, 0)) {
return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
@ -781,7 +782,7 @@ public class Transition extends SymmetricComponent {
return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
}
},
// POLYNOMIAL("Smooth polynomial",
// "A polynomial is fitted such that the nose cone profile is horizontal "+
// "at the aft end of the transition. The angle at the tip is defined by "+
@ -813,18 +814,18 @@ public class Transition extends SymmetricComponent {
// }
// }
;
// Privete fields of the shapes
private final String name;
private final String transitionDesc;
private final String noseconeDesc;
private final boolean canClip;
// Non-clippable constructor
Shape(String name, String noseconeDesc, String transitionDesc) {
this(name, noseconeDesc, transitionDesc, false);
}
// Clippable constructor
Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) {
this.name = name;
@ -832,29 +833,29 @@ public class Transition extends SymmetricComponent {
this.noseconeDesc = noseconeDesc;
this.transitionDesc = transitionDesc;
}
/**
* Return the name of the transition shape name.
*/
public String getName() {
return name;
}
/**
* Get a description of the Transition shape.
*/
public String getTransitionDescription() {
return transitionDesc;
}
/**
* Get a description of the NoseCone shape.
*/
public String getNoseConeDescription() {
return noseconeDesc;
}
/**
* Check whether the shape differs in clipped mode. The clipping should be
* enabled by default if possible.
@ -862,35 +863,35 @@ public class Transition extends SymmetricComponent {
public boolean isClippable() {
return canClip;
}
/**
* Return whether the shape uses the shape parameter. (Default false.)
*/
public boolean usesParameter() {
return false;
}
/**
* Return the minimum value of the shape parameter. (Default 0.)
*/
public double minParameter() {
return 0.0;
}
/**
* Return the maximum value of the shape parameter. (Default 1.)
*/
public double maxParameter() {
return 1.0;
}
/**
* Return the default value of the shape parameter. (Default 0.)
*/
public double defaultParameter() {
return 0.0;
}
/**
* Calculate the basic radius of a transition with the given radius, length and
* shape parameter at the point x from the tip of the component. It is assumed
@ -904,8 +905,8 @@ public class Transition extends SymmetricComponent {
* @return The basic radius at the given position.
*/
public abstract double getRadius(double x, double radius, double length, double param);
/**
* Returns the name of the shape (same as getName()).
*/
@ -913,5 +914,22 @@ public class Transition extends SymmetricComponent {
public String toString() {
return name;
}
/**
* Lookup the Shape given the localized name. This differs from the standard valueOf as that looks up
* based on the canonical name, not the localized name which is an instance var.
*
* @param localizedName
* @return
*/
public static Shape toShape(String localizedName) {
Shape[] values = Shape.values();
for (Shape value : values) {
if (value.getName().equals(localizedName)) {
return value;
}
}
return null;
}
}
}