]> git.gag.com Git - debian/openrocket/blob - src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
added svn:ignores
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / ComponentAnalysisDialog.java
1 package net.sf.openrocket.gui.dialogs;
2
3 import static net.sf.openrocket.unit.Unit.NOUNIT2;
4 import static net.sf.openrocket.util.Chars.ALPHA;
5
6 import java.awt.Color;
7 import java.awt.Component;
8 import java.awt.Dimension;
9 import java.awt.Font;
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;
16 import java.util.Map;
17 import java.util.Vector;
18
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;
37
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;
66
67 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
68         
69         private static ComponentAnalysisDialog singletonDialog = null;
70         
71
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();
79         
80         private final ColumnTableModel cpTableModel;
81         private final ColumnTableModel dragTableModel;
82         private final ColumnTableModel rollTableModel;
83         
84         private final JList warningList;
85         
86
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>();
92         
93         
94         public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
95                 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
96                 
97                 JTable table;
98                 
99                 JPanel panel = new JPanel(new MigLayout("fill", "[][35lp::][fill][fill]"));
100                 add(panel);
101                 
102                 this.configuration = rocketPanel.getConfiguration();
103                 this.aerodynamicCalculator = rocketPanel.getAerodynamicCalculator().newInstance();
104                 
105
106                 conditions = new FlightConditions(configuration);
107                 
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);
116                 
117
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() {
125                         @Override
126                         public void actionPerformed(ActionEvent e) {
127                                 stateChanged(null);
128                         }
129                 });
130                 slider.addChangeListener(new ChangeListener() {
131                         @Override
132                         public void stateChanged(ChangeEvent e) {
133                                 if (!fakeChange)
134                                         worstToggle.setSelected(false);
135                         }
136                 });
137                 panel.add(worstToggle, "");
138                 
139
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");
144                 
145
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");
149                 
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");
153                 
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");
158                 
159
160                 // Stage and motor selection:
161                 
162                 panel.add(new JLabel("Active stages:"), "spanx, split, gapafter rel");
163                 panel.add(new StageSelector(configuration), "gapafter paragraph");
164                 
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");
169                 
170
171
172                 // Tabbed pane
173                 
174                 JTabbedPane tabbedPane = new JTabbedPane();
175                 panel.add(tabbedPane, "spanx, growx, growy");
176                 
177
178                 // Create the CP data table
179                 cpTableModel = new ColumnTableModel(
180
181                 new Column("Component") {
182                         @Override
183                         public Object getValueAt(int row) {
184                                 RocketComponent c = cpData.get(row).getComponent();
185                                 if (c instanceof Rocket) {
186                                         return "Total";
187                                 }
188                                 return c.toString();
189                         }
190                         
191                         @Override
192                         public int getDefaultWidth() {
193                                 return 200;
194                         }
195                 },
196                                 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
197                                         private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
198                                         
199                                         @Override
200                                         public Object getValueAt(int row) {
201                                                 return unit.toString(cgData.get(row).x);
202                                         }
203                                 },
204                                 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
205                                         private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
206                                         
207                                         @Override
208                                         public Object getValueAt(int row) {
209                                                 return unit.toString(cgData.get(row).weight);
210                                         }
211                                 },
212                                 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
213                                         private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
214                                         
215                                         @Override
216                                         public Object getValueAt(int row) {
217                                                 return unit.toString(cpData.get(row).getCP().x);
218                                         }
219                                 },
220                                 new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
221                                         @Override
222                                         public Object getValueAt(int row) {
223                                                 return NOUNIT2.toString(cpData.get(row).getCP().weight);
224                                         }
225                                 }
226
227                 ) {
228                         @Override
229                         public int getRowCount() {
230                                 return cpData.size();
231                         }
232                 };
233                 
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());
239                 
240                 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
241                 //              table.setShowHorizontalLines(false);
242                 //              table.setShowVerticalLines(true);
243                 
244                 JScrollPane scrollpane = new JScrollPane(table);
245                 scrollpane.setPreferredSize(new Dimension(600, 200));
246                 
247                 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
248                 
249
250
251                 // Create the drag data table
252                 dragTableModel = new ColumnTableModel(
253                                 new Column("Component") {
254                                         @Override
255                                         public Object getValueAt(int row) {
256                                                 RocketComponent c = dragData.get(row).getComponent();
257                                                 if (c instanceof Rocket) {
258                                                         return "Total";
259                                                 }
260                                                 return c.toString();
261                                         }
262                                         
263                                         @Override
264                                         public int getDefaultWidth() {
265                                                 return 200;
266                                         }
267                                 },
268                                 new Column("<html>Pressure C<sub>D</sub>") {
269                                         @Override
270                                         public Object getValueAt(int row) {
271                                                 return dragData.get(row).getPressureCD();
272                                         }
273                                 },
274                                 new Column("<html>Base C<sub>D</sub>") {
275                                         @Override
276                                         public Object getValueAt(int row) {
277                                                 return dragData.get(row).getBaseCD();
278                                         }
279                                 },
280                                 new Column("<html>Friction C<sub>D</sub>") {
281                                         @Override
282                                         public Object getValueAt(int row) {
283                                                 return dragData.get(row).getFrictionCD();
284                                         }
285                                 },
286                                 new Column("<html>Total C<sub>D</sub>") {
287                                         @Override
288                                         public Object getValueAt(int row) {
289                                                 return dragData.get(row).getCD();
290                                         }
291                                 }
292                                 ) {
293                                         @Override
294                                         public int getRowCount() {
295                                                 return dragData.size();
296                                         }
297                                 };
298                 
299
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());
305                 
306                 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f, 1.0f, 0.5f)));
307                 //              table.setShowHorizontalLines(false);
308                 //              table.setShowVerticalLines(true);
309                 
310                 scrollpane = new JScrollPane(table);
311                 scrollpane.setPreferredSize(new Dimension(600, 200));
312                 
313                 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
314                 
315
316
317
318                 // Create the roll data table
319                 rollTableModel = new ColumnTableModel(
320                                 new Column("Component") {
321                                         @Override
322                                         public Object getValueAt(int row) {
323                                                 RocketComponent c = rollData.get(row).getComponent();
324                                                 if (c instanceof Rocket) {
325                                                         return "Total";
326                                                 }
327                                                 return c.toString();
328                                         }
329                                 },
330                                 new Column("Roll forcing coefficient") {
331                                         @Override
332                                         public Object getValueAt(int row) {
333                                                 return rollData.get(row).getCrollForce();
334                                         }
335                                 },
336                                 new Column("Roll damping coefficient") {
337                                         @Override
338                                         public Object getValueAt(int row) {
339                                                 return rollData.get(row).getCrollDamp();
340                                         }
341                                 },
342                                 new Column("<html>Total C<sub>l</sub>") {
343                                         @Override
344                                         public Object getValueAt(int row) {
345                                                 return rollData.get(row).getCroll();
346                                         }
347                                 }
348                                 ) {
349                                         @Override
350                                         public int getRowCount() {
351                                                 return rollData.size();
352                                         }
353                                 };
354                 
355
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());
361                 
362                 scrollpane = new JScrollPane(table);
363                 scrollpane.setPreferredSize(new Dimension(600, 200));
364                 
365                 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
366                 
367
368
369
370
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);
378                 
379
380
381                 // Remove listeners when closing window
382                 this.addWindowListener(new WindowAdapter() {
383                         @Override
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;
397                         }
398                 });
399                 
400
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);
405                 sel.resizeFont(-1);
406                 panel.add(sel, "gapright para");
407                 
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);
411                 sel.resizeFont(-1);
412                 panel.add(sel, "wrap");
413                 
414
415
416                 // Buttons
417                 JButton button;
418                 
419                 // TODO: LOW: printing
420                 //              button = new JButton("Print");
421                 //              button.addActionListener(new ActionListener() {
422                 //                      public void actionPerformed(ActionEvent e) {
423                 //                              try {
424                 //                                      table.print();
425                 //                              } catch (PrinterException e1) {
426                 //                                      JOptionPane.showMessageDialog(ComponentAnalysisDialog.this, 
427                 //                                                      "An error occurred while printing.", "Print error",
428                 //                                                      JOptionPane.ERROR_MESSAGE);
429                 //                              }
430                 //                      }
431                 //              });
432                 //              panel.add(button,"tag ok");
433                 
434                 button = new JButton("Close");
435                 button.addActionListener(new ActionListener() {
436                         public void actionPerformed(ActionEvent e) {
437                                 ComponentAnalysisDialog.this.dispose();
438                         }
439                 });
440                 panel.add(button, "span, split, tag cancel");
441                 
442
443                 this.setLocationByPlatform(true);
444                 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
445                 pack();
446                 
447                 GUIUtil.setDisposableDialogOptions(this, null);
448         }
449         
450         
451
452         /**
453          * Updates the data in the table and fires a table data change event.
454          */
455         @Override
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);
464                 
465                 if (worstToggle.isSelected()) {
466                         aerodynamicCalculator.getWorstCP(configuration, conditions, null);
467                         if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
468                                 fakeChange = true;
469                                 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
470                                 fakeChange = false;
471                                 return;
472                         }
473                 }
474                 
475                 Map<RocketComponent, AerodynamicForces> aeroData =
476                                 aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
477                 Map<RocketComponent, Coordinate> massData =
478                                 massCalculator.getCGAnalysis(configuration, MassCalcType.LAUNCH_MASS);
479                 
480
481                 cpData.clear();
482                 cgData.clear();
483                 dragData.clear();
484                 rollData.clear();
485                 for (RocketComponent c : configuration) {
486                         forces = aeroData.get(c);
487                         Coordinate cg = massData.get(c);
488                         
489                         if (forces == null)
490                                 continue;
491                         if (forces.getCP() != null) {
492                                 cpData.add(forces);
493                                 cgData.add(cg);
494                         }
495                         if (!Double.isNaN(forces.getCD())) {
496                                 dragData.add(forces);
497                         }
498                         if (c instanceof FinSet) {
499                                 rollData.add(forces);
500                         }
501                 }
502                 forces = aeroData.get(configuration.getRocket());
503                 if (forces != null) {
504                         cpData.add(forces);
505                         cgData.add(massData.get(configuration.getRocket()));
506                         dragData.add(forces);
507                         rollData.add(forces);
508                         totalCD = forces.getCD();
509                 } else {
510                         totalCD = 0;
511                 }
512                 
513                 // Set warnings
514                 if (set.isEmpty()) {
515                         warningList.setListData(new String[] {
516                                         "<html><i><font color=\"gray\">No warnings.</font></i>"
517                         });
518                 } else {
519                         warningList.setListData(new Vector<Warning>(set));
520                 }
521                 
522                 cpTableModel.fireTableDataChanged();
523                 dragTableModel.fireTableDataChanged();
524                 rollTableModel.fireTableDataChanged();
525         }
526         
527         
528         private class CustomCellRenderer extends JLabel implements TableCellRenderer {
529                 private final Font normalFont;
530                 private final Font boldFont;
531                 
532                 public CustomCellRenderer() {
533                         super();
534                         normalFont = getFont();
535                         boldFont = normalFont.deriveFont(Font.BOLD);
536                 }
537                 
538                 @Override
539                 public Component getTableCellRendererComponent(JTable table, Object value,
540                                 boolean isSelected, boolean hasFocus, int row, int column) {
541                         
542                         this.setText(value.toString());
543                         
544                         if ((row < 0) || (row >= cpData.size()))
545                                 return this;
546                         
547                         if (cpData.get(row).getComponent() instanceof Rocket) {
548                                 this.setFont(boldFont);
549                         } else {
550                                 this.setFont(normalFont);
551                         }
552                         return this;
553                 }
554         }
555         
556         
557
558         private class DragCellRenderer extends JLabel implements TableCellRenderer {
559                 private final Font normalFont;
560                 private final Font boldFont;
561                 
562                 
563                 public DragCellRenderer(Color baseColor) {
564                         super();
565                         normalFont = getFont();
566                         boldFont = normalFont.deriveFont(Font.BOLD);
567                 }
568                 
569                 @Override
570                 public Component getTableCellRendererComponent(JTable table, Object value,
571                                 boolean isSelected, boolean hasFocus, int row, int column) {
572                         
573                         if (value instanceof Double) {
574                                 
575                                 // A drag coefficient
576                                 double cd = (Double) value;
577                                 this.setText(String.format("%.2f (%.0f%%)", cd, 100 * cd / totalCD));
578                                 
579                                 float r = (float) (cd / 1.5);
580                                 
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);
583                                 float val = 1.0f;
584                                 
585                                 this.setBackground(Color.getHSBColor(hue, sat, val));
586                                 this.setOpaque(true);
587                                 this.setHorizontalAlignment(SwingConstants.CENTER);
588                                 
589                         } else {
590                                 
591                                 // Other
592                                 this.setText(value.toString());
593                                 this.setOpaque(false);
594                                 this.setHorizontalAlignment(SwingConstants.LEFT);
595                                 
596                         }
597                         
598                         if ((row < 0) || (row >= dragData.size()))
599                                 return this;
600                         
601                         if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) {
602                                 this.setFont(boldFont);
603                         } else {
604                                 this.setFont(normalFont);
605                         }
606                         return this;
607                 }
608         }
609         
610         
611         /////////  Singleton implementation
612         
613         public static void showDialog(RocketPanel rocketpanel) {
614                 if (singletonDialog != null)
615                         singletonDialog.dispose();
616                 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
617                 singletonDialog.setVisible(true);
618         }
619         
620         public static void hideDialog() {
621                 if (singletonDialog != null)
622                         singletonDialog.dispose();
623         }
624         
625 }