Initial GUI for editing and saving component presets. See ComponentPresetPanel.main() for standalone execution.
This commit is contained in:
parent
0a199d73c3
commit
3d73e9d9eb
@ -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×<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×<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×<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> × (<i>x</i> / <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> × (<i>x</i> / <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.
|
||||
|
229
core/src/net/sf/openrocket/gui/preset/ComponentPresetPanel.java
Normal file
229
core/src/net/sf/openrocket/gui/preset/ComponentPresetPanel.java
Normal 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);
|
||||
}
|
||||
}
|
105
core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java
Normal file
105
core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
120
core/src/net/sf/openrocket/gui/preset/MaterialModel.java
Normal file
120
core/src/net/sf/openrocket/gui/preset/MaterialModel.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
1636
core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java
Normal file
1636
core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
|
||||
package net.sf.openrocket.gui.preset;
|
||||
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
|
||||
/**
|
||||
*/
|
||||
public interface PresetResultListener {
|
||||
|
||||
void notifyResult(ComponentPreset preset);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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×<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×<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×<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user