1 package net.sf.openrocket.gui.dialogs;
3 import static net.sf.openrocket.unit.Unit.NOUNIT2;
4 import static net.sf.openrocket.util.Chars.ALPHA;
7 import java.awt.Component;
8 import java.awt.Dimension;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.WindowAdapter;
13 import java.awt.event.WindowEvent;
14 import java.util.ArrayList;
15 import java.util.List;
17 import java.util.Vector;
19 import javax.swing.BorderFactory;
20 import javax.swing.JButton;
21 import javax.swing.JComboBox;
22 import javax.swing.JDialog;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JList;
26 import javax.swing.JPanel;
27 import javax.swing.JScrollPane;
28 import javax.swing.JTabbedPane;
29 import javax.swing.JTable;
30 import javax.swing.JToggleButton;
31 import javax.swing.ListSelectionModel;
32 import javax.swing.SwingConstants;
33 import javax.swing.SwingUtilities;
34 import javax.swing.event.ChangeEvent;
35 import javax.swing.event.ChangeListener;
36 import javax.swing.table.TableCellRenderer;
38 import net.miginfocom.swing.MigLayout;
39 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
40 import net.sf.openrocket.aerodynamics.AerodynamicForces;
41 import net.sf.openrocket.aerodynamics.FlightConditions;
42 import net.sf.openrocket.aerodynamics.Warning;
43 import net.sf.openrocket.aerodynamics.WarningSet;
44 import net.sf.openrocket.gui.adaptors.Column;
45 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
46 import net.sf.openrocket.gui.adaptors.DoubleModel;
47 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
48 import net.sf.openrocket.gui.components.BasicSlider;
49 import net.sf.openrocket.gui.components.StageSelector;
50 import net.sf.openrocket.gui.components.StyledLabel;
51 import net.sf.openrocket.gui.components.UnitSelector;
52 import net.sf.openrocket.gui.scalefigure.RocketPanel;
53 import net.sf.openrocket.masscalc.BasicMassCalculator;
54 import net.sf.openrocket.masscalc.MassCalculator;
55 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
56 import net.sf.openrocket.rocketcomponent.Configuration;
57 import net.sf.openrocket.rocketcomponent.FinSet;
58 import net.sf.openrocket.rocketcomponent.Rocket;
59 import net.sf.openrocket.rocketcomponent.RocketComponent;
60 import net.sf.openrocket.unit.Unit;
61 import net.sf.openrocket.unit.UnitGroup;
62 import net.sf.openrocket.util.Coordinate;
63 import net.sf.openrocket.util.GUIUtil;
64 import net.sf.openrocket.util.MathUtil;
65 import net.sf.openrocket.util.Prefs;
67 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
69 private static ComponentAnalysisDialog singletonDialog = null;
72 private final FlightConditions conditions;
73 private final Configuration configuration;
74 private final DoubleModel theta, aoa, mach, roll;
75 private final JToggleButton worstToggle;
76 private boolean fakeChange = false;
77 private AerodynamicCalculator aerodynamicCalculator;
78 private final MassCalculator massCalculator = new BasicMassCalculator();
80 private final ColumnTableModel cpTableModel;
81 private final ColumnTableModel dragTableModel;
82 private final ColumnTableModel rollTableModel;
84 private final JList warningList;
87 private final List<AerodynamicForces> cpData = new ArrayList<AerodynamicForces>();
88 private final List<Coordinate> cgData = new ArrayList<Coordinate>();
89 private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
90 private double totalCD = 0;
91 private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
94 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
95 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
99 JPanel panel = new JPanel(new MigLayout("fill", "[][35lp::][fill][fill]"));
102 this.configuration = rocketPanel.getConfiguration();
103 this.aerodynamicCalculator = rocketPanel.getAerodynamicCalculator().newInstance();
106 conditions = new FlightConditions(configuration);
108 rocketPanel.setCPAOA(0);
109 aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI);
110 rocketPanel.setCPMach(Prefs.getDefaultMach());
111 mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0);
112 rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation());
113 theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
114 rocketPanel.setCPRoll(0);
115 roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
118 panel.add(new JLabel("Wind direction:"), "width 100lp!");
119 panel.add(new UnitSelector(theta, true), "width 50lp!");
120 BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI));
121 panel.add(slider, "growx, split 2");
122 worstToggle = new JToggleButton("Worst");
123 worstToggle.setSelected(true);
124 worstToggle.addActionListener(new ActionListener() {
126 public void actionPerformed(ActionEvent e) {
130 slider.addChangeListener(new ChangeListener() {
132 public void stateChanged(ChangeEvent e) {
134 worstToggle.setSelected(false);
137 panel.add(worstToggle, "");
140 warningList = new JList();
141 JScrollPane scrollPane = new JScrollPane(warningList);
142 scrollPane.setBorder(BorderFactory.createTitledBorder("Warnings:"));
143 panel.add(scrollPane, "gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap");
146 panel.add(new JLabel("Angle of attack:"), "width 100lp!");
147 panel.add(new UnitSelector(aoa, true), "width 50lp!");
148 panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)), "growx, wrap");
150 panel.add(new JLabel("Mach number:"), "width 100lp!");
151 panel.add(new UnitSelector(mach, true), "width 50lp!");
152 panel.add(new BasicSlider(mach.getSliderModel(0, 3)), "growx, wrap");
154 panel.add(new JLabel("Roll rate:"), "width 100lp!");
155 panel.add(new UnitSelector(roll, true), "width 50lp!");
156 panel.add(new BasicSlider(roll.getSliderModel(-20 * 2 * Math.PI, 20 * 2 * Math.PI)),
157 "growx, wrap paragraph");
160 // Stage and motor selection:
162 panel.add(new JLabel("Active stages:"), "spanx, split, gapafter rel");
163 panel.add(new StageSelector(configuration), "gapafter paragraph");
165 JLabel label = new JLabel("Motor configuration:");
166 label.setHorizontalAlignment(JLabel.RIGHT);
167 panel.add(label, "growx, right");
168 panel.add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap");
174 JTabbedPane tabbedPane = new JTabbedPane();
175 panel.add(tabbedPane, "spanx, growx, growy");
178 // Create the CP data table
179 cpTableModel = new ColumnTableModel(
181 new Column("Component") {
183 public Object getValueAt(int row) {
184 RocketComponent c = cpData.get(row).getComponent();
185 if (c instanceof Rocket) {
192 public int getDefaultWidth() {
196 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
197 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
200 public Object getValueAt(int row) {
201 return unit.toString(cgData.get(row).x);
204 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
205 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
208 public Object getValueAt(int row) {
209 return unit.toString(cgData.get(row).weight);
212 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
213 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
216 public Object getValueAt(int row) {
217 return unit.toString(cpData.get(row).getCP().x);
220 new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
222 public Object getValueAt(int row) {
223 return NOUNIT2.toString(cpData.get(row).getCP().weight);
229 public int getRowCount() {
230 return cpData.size();
234 table = new JTable(cpTableModel);
235 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
236 table.setSelectionBackground(Color.LIGHT_GRAY);
237 table.setSelectionForeground(Color.BLACK);
238 cpTableModel.setColumnWidths(table.getColumnModel());
240 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
241 // table.setShowHorizontalLines(false);
242 // table.setShowVerticalLines(true);
244 JScrollPane scrollpane = new JScrollPane(table);
245 scrollpane.setPreferredSize(new Dimension(600, 200));
247 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
251 // Create the drag data table
252 dragTableModel = new ColumnTableModel(
253 new Column("Component") {
255 public Object getValueAt(int row) {
256 RocketComponent c = dragData.get(row).getComponent();
257 if (c instanceof Rocket) {
264 public int getDefaultWidth() {
268 new Column("<html>Pressure C<sub>D</sub>") {
270 public Object getValueAt(int row) {
271 return dragData.get(row).getPressureCD();
274 new Column("<html>Base C<sub>D</sub>") {
276 public Object getValueAt(int row) {
277 return dragData.get(row).getBaseCD();
280 new Column("<html>Friction C<sub>D</sub>") {
282 public Object getValueAt(int row) {
283 return dragData.get(row).getFrictionCD();
286 new Column("<html>Total C<sub>D</sub>") {
288 public Object getValueAt(int row) {
289 return dragData.get(row).getCD();
294 public int getRowCount() {
295 return dragData.size();
300 table = new JTable(dragTableModel);
301 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
302 table.setSelectionBackground(Color.LIGHT_GRAY);
303 table.setSelectionForeground(Color.BLACK);
304 dragTableModel.setColumnWidths(table.getColumnModel());
306 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f, 1.0f, 0.5f)));
307 // table.setShowHorizontalLines(false);
308 // table.setShowVerticalLines(true);
310 scrollpane = new JScrollPane(table);
311 scrollpane.setPreferredSize(new Dimension(600, 200));
313 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
318 // Create the roll data table
319 rollTableModel = new ColumnTableModel(
320 new Column("Component") {
322 public Object getValueAt(int row) {
323 RocketComponent c = rollData.get(row).getComponent();
324 if (c instanceof Rocket) {
330 new Column("Roll forcing coefficient") {
332 public Object getValueAt(int row) {
333 return rollData.get(row).getCrollForce();
336 new Column("Roll damping coefficient") {
338 public Object getValueAt(int row) {
339 return rollData.get(row).getCrollDamp();
342 new Column("<html>Total C<sub>l</sub>") {
344 public Object getValueAt(int row) {
345 return rollData.get(row).getCroll();
350 public int getRowCount() {
351 return rollData.size();
356 table = new JTable(rollTableModel);
357 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
358 table.setSelectionBackground(Color.LIGHT_GRAY);
359 table.setSelectionForeground(Color.BLACK);
360 rollTableModel.setColumnWidths(table.getColumnModel());
362 scrollpane = new JScrollPane(table);
363 scrollpane.setPreferredSize(new Dimension(600, 200));
365 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
371 // Add the data updater to listen to changes in aoa and theta
372 mach.addChangeListener(this);
373 theta.addChangeListener(this);
374 aoa.addChangeListener(this);
375 roll.addChangeListener(this);
376 configuration.addChangeListener(this);
377 this.stateChanged(null);
381 // Remove listeners when closing window
382 this.addWindowListener(new WindowAdapter() {
384 public void windowClosed(WindowEvent e) {
385 System.out.println("Closing method called: " + this);
386 theta.removeChangeListener(ComponentAnalysisDialog.this);
387 aoa.removeChangeListener(ComponentAnalysisDialog.this);
388 mach.removeChangeListener(ComponentAnalysisDialog.this);
389 roll.removeChangeListener(ComponentAnalysisDialog.this);
390 configuration.removeChangeListener(ComponentAnalysisDialog.this);
391 System.out.println("SETTING NAN VALUES");
392 rocketPanel.setCPAOA(Double.NaN);
393 rocketPanel.setCPTheta(Double.NaN);
394 rocketPanel.setCPMach(Double.NaN);
395 rocketPanel.setCPRoll(Double.NaN);
396 singletonDialog = null;
401 panel.add(new StyledLabel("Reference length: ", -1),
402 "span, split, gapleft para, gapright rel");
403 DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
404 UnitSelector sel = new UnitSelector(dm, true);
406 panel.add(sel, "gapright para");
408 panel.add(new StyledLabel("Reference area: ", -1), "gapright rel");
409 dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
410 sel = new UnitSelector(dm, true);
412 panel.add(sel, "wrap");
419 // TODO: LOW: printing
420 // button = new JButton("Print");
421 // button.addActionListener(new ActionListener() {
422 // public void actionPerformed(ActionEvent e) {
425 // } catch (PrinterException e1) {
426 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
427 // "An error occurred while printing.", "Print error",
428 // JOptionPane.ERROR_MESSAGE);
432 // panel.add(button,"tag ok");
434 button = new JButton("Close");
435 button.addActionListener(new ActionListener() {
436 public void actionPerformed(ActionEvent e) {
437 ComponentAnalysisDialog.this.dispose();
440 panel.add(button, "span, split, tag cancel");
443 this.setLocationByPlatform(true);
444 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
447 GUIUtil.setDisposableDialogOptions(this, null);
453 * Updates the data in the table and fires a table data change event.
456 public void stateChanged(ChangeEvent e) {
457 AerodynamicForces forces;
458 WarningSet set = new WarningSet();
459 conditions.setAOA(aoa.getValue());
460 conditions.setTheta(theta.getValue());
461 conditions.setMach(mach.getValue());
462 conditions.setRollRate(roll.getValue());
463 conditions.setReference(configuration);
465 if (worstToggle.isSelected()) {
466 aerodynamicCalculator.getWorstCP(configuration, conditions, null);
467 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
469 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
475 Map<RocketComponent, AerodynamicForces> aeroData =
476 aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
477 Map<RocketComponent, Coordinate> massData =
478 massCalculator.getCGAnalysis(configuration, MassCalcType.LAUNCH_MASS);
485 for (RocketComponent c : configuration) {
486 forces = aeroData.get(c);
487 Coordinate cg = massData.get(c);
491 if (forces.getCP() != null) {
495 if (!Double.isNaN(forces.getCD())) {
496 dragData.add(forces);
498 if (c instanceof FinSet) {
499 rollData.add(forces);
502 forces = aeroData.get(configuration.getRocket());
503 if (forces != null) {
505 cgData.add(massData.get(configuration.getRocket()));
506 dragData.add(forces);
507 rollData.add(forces);
508 totalCD = forces.getCD();
515 warningList.setListData(new String[] {
516 "<html><i><font color=\"gray\">No warnings.</font></i>"
519 warningList.setListData(new Vector<Warning>(set));
522 cpTableModel.fireTableDataChanged();
523 dragTableModel.fireTableDataChanged();
524 rollTableModel.fireTableDataChanged();
528 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
529 private final Font normalFont;
530 private final Font boldFont;
532 public CustomCellRenderer() {
534 normalFont = getFont();
535 boldFont = normalFont.deriveFont(Font.BOLD);
539 public Component getTableCellRendererComponent(JTable table, Object value,
540 boolean isSelected, boolean hasFocus, int row, int column) {
542 this.setText(value.toString());
544 if ((row < 0) || (row >= cpData.size()))
547 if (cpData.get(row).getComponent() instanceof Rocket) {
548 this.setFont(boldFont);
550 this.setFont(normalFont);
558 private class DragCellRenderer extends JLabel implements TableCellRenderer {
559 private final Font normalFont;
560 private final Font boldFont;
563 public DragCellRenderer(Color baseColor) {
565 normalFont = getFont();
566 boldFont = normalFont.deriveFont(Font.BOLD);
570 public Component getTableCellRendererComponent(JTable table, Object value,
571 boolean isSelected, boolean hasFocus, int row, int column) {
573 if (value instanceof Double) {
575 // A drag coefficient
576 double cd = (Double) value;
577 this.setText(String.format("%.2f (%.0f%%)", cd, 100 * cd / totalCD));
579 float r = (float) (cd / 1.5);
581 float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f);
582 float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1);
585 this.setBackground(Color.getHSBColor(hue, sat, val));
586 this.setOpaque(true);
587 this.setHorizontalAlignment(SwingConstants.CENTER);
592 this.setText(value.toString());
593 this.setOpaque(false);
594 this.setHorizontalAlignment(SwingConstants.LEFT);
598 if ((row < 0) || (row >= dragData.size()))
601 if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) {
602 this.setFont(boldFont);
604 this.setFont(normalFont);
611 ///////// Singleton implementation
613 public static void showDialog(RocketPanel rocketpanel) {
614 if (singletonDialog != null)
615 singletonDialog.dispose();
616 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
617 singletonDialog.setVisible(true);
620 public static void hideDialog() {
621 if (singletonDialog != null)
622 singletonDialog.dispose();