1 package com.billkuker.rocketry.motorsim.visual.workbench;
\r
3 import java.awt.BorderLayout;
\r
4 import java.awt.Color;
\r
5 import java.awt.Dimension;
\r
6 import java.awt.event.ActionEvent;
\r
7 import java.awt.event.ActionListener;
\r
8 import java.awt.event.ComponentEvent;
\r
9 import java.awt.event.ComponentListener;
\r
10 import java.awt.event.FocusEvent;
\r
11 import java.awt.event.FocusListener;
\r
12 import java.beans.PropertyChangeEvent;
\r
13 import java.beans.PropertyChangeListener;
\r
14 import java.beans.PropertyVetoException;
\r
15 import java.net.URI;
\r
16 import java.util.List;
\r
17 import java.util.Vector;
\r
19 import javax.measure.quantity.Duration;
\r
20 import javax.measure.unit.SI;
\r
21 import javax.swing.Box;
\r
22 import javax.swing.BoxLayout;
\r
23 import javax.swing.DefaultComboBoxModel;
\r
24 import javax.swing.JComboBox;
\r
25 import javax.swing.JFrame;
\r
26 import javax.swing.JLabel;
\r
27 import javax.swing.JPanel;
\r
28 import javax.swing.JSplitPane;
\r
29 import javax.swing.JTabbedPane;
\r
30 import javax.swing.JTextArea;
\r
31 import javax.swing.JTextField;
\r
32 import javax.swing.SwingUtilities;
\r
33 import javax.swing.UIManager;
\r
34 import javax.swing.WindowConstants;
\r
36 import org.apache.log4j.Logger;
\r
37 import org.jscience.physics.amount.Amount;
\r
39 import com.billkuker.rocketry.motorsim.Burn;
\r
40 import com.billkuker.rocketry.motorsim.Chamber;
\r
41 import com.billkuker.rocketry.motorsim.ChangeListening;
\r
42 import com.billkuker.rocketry.motorsim.Colors;
\r
43 import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
\r
44 import com.billkuker.rocketry.motorsim.CylindricalChamber;
\r
45 import com.billkuker.rocketry.motorsim.Fuel;
\r
46 import com.billkuker.rocketry.motorsim.Grain;
\r
47 import com.billkuker.rocketry.motorsim.Motor;
\r
48 import com.billkuker.rocketry.motorsim.Nozzle;
\r
49 import com.billkuker.rocketry.motorsim.cases.Schedule40;
\r
50 import com.billkuker.rocketry.motorsim.cases.Schedule80;
\r
51 import com.billkuker.rocketry.motorsim.fuel.FuelResolver;
\r
52 import com.billkuker.rocketry.motorsim.fuel.KNSU;
\r
53 import com.billkuker.rocketry.motorsim.grain.CSlot;
\r
54 import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;
\r
55 import com.billkuker.rocketry.motorsim.grain.EndBurner;
\r
56 import com.billkuker.rocketry.motorsim.grain.Finocyl;
\r
57 import com.billkuker.rocketry.motorsim.grain.Moonburner;
\r
58 import com.billkuker.rocketry.motorsim.grain.MultiGrain;
\r
59 import com.billkuker.rocketry.motorsim.grain.MultiPort;
\r
60 import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;
\r
61 import com.billkuker.rocketry.motorsim.grain.Square;
\r
62 import com.billkuker.rocketry.motorsim.grain.Star;
\r
63 import com.billkuker.rocketry.motorsim.visual.BurnPanel;
\r
64 import com.billkuker.rocketry.motorsim.visual.ClassChooser;
\r
65 import com.billkuker.rocketry.motorsim.visual.Editor;
\r
66 import com.billkuker.rocketry.motorsim.visual.GrainPanel;
\r
67 import com.billkuker.rocketry.motorsim.visual.HardwarePanel;
\r
68 import com.billkuker.rocketry.motorsim.visual.SummaryPanel;
\r
70 public class MotorEditor extends JPanel implements PropertyChangeListener, FuelResolver.FuelsChangeListener{
\r
71 private static final long serialVersionUID = 1L;
\r
72 private static Logger log = Logger.getLogger(MotorEditor.class);
\r
74 GrainEditor grainEditor;
\r
80 boolean closed = false;
\r
82 private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();
\r
83 private DefaultComboBoxModel availableFuels = new DefaultComboBoxModel();
\r
85 public MotorEditor(Motor m) {
\r
86 setLayout( new BorderLayout());
\r
87 tabs = new JTabbedPane(JTabbedPane.TOP);
\r
88 add(tabs, BorderLayout.CENTER);
\r
89 availableFuels.addElement(m.getFuel());
\r
90 availableFuels.setSelectedItem(m.getFuel());
\r
91 FuelResolver.addFuelsChangeListener(this);
\r
95 Burn.getBurnSettings().addPropertyChangeListener(this);
\r
99 public void fuelsChanged() {
\r
100 while ( availableFuels.getSize() > 0 && availableFuels.getIndexOf(availableFuels.getSelectedItem()) != 0 )
\r
101 availableFuels.removeElementAt(0);
\r
102 while ( availableFuels.getSize() > 1 )
\r
103 availableFuels.removeElementAt(1);
\r
104 for ( Fuel f : FuelResolver.getFuelMap().values() ){
\r
105 if ( f != availableFuels.getSelectedItem() )
\r
106 availableFuels.addElement(f);
\r
110 //private static final int XML_TAB = 0;
\r
111 private static final int CASING_TAB = 0;
\r
112 private static final int GRAIN_TAB = 1;
\r
113 private static final int BURN_TAB = 2;
\r
115 private List<Class<? extends Grain>> grainTypes = new Vector<Class<? extends Grain>>();
\r
117 grainTypes.add(CoredCylindricalGrain.class);
\r
118 grainTypes.add(Finocyl.class);
\r
119 grainTypes.add(Star.class);
\r
120 grainTypes.add(Moonburner.class);
\r
121 grainTypes.add(RodAndTubeGrain.class);
\r
122 grainTypes.add(CSlot.class);
\r
123 grainTypes.add(EndBurner.class);
\r
124 grainTypes.add(MultiPort.class);
\r
125 grainTypes.add(Square.class);
\r
128 private List<Class<? extends Chamber>> chamberTypes = new Vector<Class<? extends Chamber>>();
\r
130 chamberTypes.add(CylindricalChamber.class);
\r
131 chamberTypes.add(Schedule40.class);
\r
132 chamberTypes.add(Schedule80.class);
\r
135 private class BurnTab extends JPanel {
\r
136 private static final long serialVersionUID = 1L;
\r
137 private Thread currentThread;
\r
140 setLayout(new BorderLayout());
\r
141 setName("Simulation Results");
\r
145 private class BurnCanceled extends RuntimeException{
\r
146 private static final long serialVersionUID = 1L;
\r
149 public void reBurn() {
\r
151 if ( error != null ){
\r
152 MotorEditor.this.remove(error);
\r
156 MotorEditor.this.remove(sp);
\r
159 currentThread = new Thread() {
\r
161 setName("Burn " + motor.getName());
\r
164 public void run() {
\r
165 final Thread me = this;
\r
167 final Burn b = new Burn(motor);
\r
168 b.addBurnProgressListener(
\r
169 new Burn.BurnProgressListener() {
\r
171 public void burnComplete(){};
\r
173 public void setProgress(float f) {
\r
174 if ( currentThread != me ){
\r
175 log.info("Cancel burn on change");
\r
176 throw new BurnCanceled();
\r
179 log.info("Cancel burn on close");
\r
180 throw new BurnCanceled();
\r
185 MotorEditor.this.add(sp = new SummaryPanel(b), BorderLayout.NORTH);
\r
189 final BurnPanel bp = new BurnPanel(b);
\r
190 SwingUtilities.invokeLater(new Thread() {
\r
191 public void run() {
\r
192 add(bp, BorderLayout.CENTER);
\r
193 for (BurnWatcher bw : burnWatchers)
\r
194 bw.replace(burn, b);
\r
199 } catch (BurnCanceled c){
\r
200 log.info("Burn Canceled!");
\r
201 } catch (final Exception e) {
\r
202 SwingUtilities.invokeLater(new Thread() {
\r
203 public void run() {
\r
205 MotorEditor.this.remove(sp);
\r
206 error = new JTextArea(e.getMessage());
\r
207 error.setBackground(Colors.RED);
\r
208 error.setForeground(Color.WHITE);
\r
209 error.setEditable(false);
\r
210 MotorEditor.this.add(error, BorderLayout.NORTH);
\r
217 currentThread.start();
\r
221 private class GrainEditor extends JSplitPane {
\r
222 private static final long serialVersionUID = 1L;
\r
224 public GrainEditor(final Grain g) {
\r
225 super(JSplitPane.HORIZONTAL_SPLIT);
\r
226 setName("Grain Geometry");
\r
227 setRightComponent(new GrainPanel(g));
\r
228 if (g instanceof Grain.Composite) {
\r
229 final JPanel p = new JPanel();
\r
230 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
\r
232 Editor grainEditor = new Editor(g);
\r
233 grainEditor.setAlignmentX(LEFT_ALIGNMENT);
\r
234 p.add(grainEditor);
\r
236 for (Grain gg : ((Grain.Composite) g).getGrains()) {
\r
237 final int grainEditorIndex = p.getComponentCount() + 2;
\r
239 JLabel l = new JLabel("Grain Type:");
\r
240 l.setAlignmentX(LEFT_ALIGNMENT);
\r
243 p.add(new ClassChooser<Grain>(grainTypes, gg) {
\r
244 private static final long serialVersionUID = 1L;
\r
245 {setAlignmentX(LEFT_ALIGNMENT);}
\r
247 protected Grain classSelected(
\r
248 Class<? extends Grain> clazz, Grain ng) {
\r
251 ng = clazz.newInstance();
\r
252 } catch (InstantiationException e) {
\r
253 e.printStackTrace();
\r
254 } catch (IllegalAccessException e) {
\r
255 e.printStackTrace();
\r
258 if (g instanceof MultiGrain) {
\r
259 ((MultiGrain) g).setGrain(ng);
\r
260 p.remove(grainEditorIndex);
\r
261 p.add(new Editor(ng), grainEditorIndex);
\r
263 p.add(new Editor(g), 0);
\r
270 Editor ggEditor = new Editor(gg);
\r
271 ggEditor.setAlignmentX(LEFT_ALIGNMENT);
\r
274 if (gg instanceof ChangeListening.Subject) {
\r
275 ((ChangeListening.Subject) gg)
\r
276 .addPropertyChangeListener(MotorEditor.this);
\r
279 setLeftComponent(p);
\r
281 setLeftComponent(new Editor(g));
\r
283 // setDividerLocation(.25);
\r
284 // setResizeWeight(.25);
\r
285 if (g instanceof ChangeListening.Subject) {
\r
286 ((ChangeListening.Subject) g)
\r
287 .addPropertyChangeListener(MotorEditor.this);
\r
292 private class CaseEditor extends JSplitPane implements ComponentListener {
\r
293 private static final long serialVersionUID = 1L;
\r
295 private HardwarePanel hp;
\r
296 private JPanel casing;
\r
297 private JPanel nozzle;
\r
298 private Editor casingEditor;
\r
299 private Editor nozzleEditor;
\r
301 private void setup() {
\r
302 if (casingEditor != null)
\r
303 casing.remove(casingEditor);
\r
304 casingEditor = new Editor(motor.getChamber());
\r
305 casingEditor.setAlignmentX(LEFT_ALIGNMENT);
\r
306 casing.add(casingEditor);
\r
308 if (nozzleEditor != null)
\r
309 nozzle.remove(nozzleEditor);
\r
310 nozzleEditor = new Editor(motor.getNozzle());
\r
311 nozzleEditor.setAlignmentX(LEFT_ALIGNMENT);
\r
312 nozzle.add(nozzleEditor);
\r
316 setBottomComponent(hp = new HardwarePanel(motor));
\r
317 if (motor.getNozzle() instanceof ChangeListening.Subject) {
\r
318 ((ChangeListening.Subject) motor.getNozzle())
\r
319 .addPropertyChangeListener(MotorEditor.this);
\r
321 if (motor.getChamber() instanceof ChangeListening.Subject) {
\r
322 ((ChangeListening.Subject) motor.getChamber())
\r
323 .addPropertyChangeListener(MotorEditor.this);
\r
325 if (motor.getFuel() instanceof ChangeListening.Subject ){
\r
326 ((ChangeListening.Subject) motor.getFuel())
\r
327 .addPropertyChangeListener(MotorEditor.this);
\r
331 public CaseEditor() {
\r
332 super(JSplitPane.VERTICAL_SPLIT);
\r
333 setName("General Parameters");
\r
334 this.addComponentListener(this);
\r
336 JPanel parts = new JPanel();
\r
337 parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));
\r
338 setTopComponent(parts);
\r
340 JPanel nameAndFuel = new JPanel();
\r
341 nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));
\r
343 JLabel l = new JLabel("Name:");
\r
344 l.setAlignmentX(LEFT_ALIGNMENT);
\r
345 nameAndFuel.add(l);
\r
346 nameAndFuel.add(new JTextField(motor.getName()) {
\r
347 private static final long serialVersionUID = 1L;
\r
349 setAlignmentX(LEFT_ALIGNMENT);
\r
350 setMinimumSize(new Dimension(200, 20));
\r
351 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
352 final JTextField t = this;
\r
353 addFocusListener(new FocusListener() {
\r
356 public void focusLost(FocusEvent e) {
\r
357 String n = t.getText();
\r
358 if (!"".equals(n) && !n.equals(motor.getName())) {
\r
361 t.setText(motor.getName());
\r
366 public void focusGained(FocusEvent e) {
\r
374 l = new JLabel("Fuel:");
\r
375 l.setAlignmentX(LEFT_ALIGNMENT);
\r
376 nameAndFuel.add(l);
\r
378 nameAndFuel.add( new JComboBox(availableFuels){
\r
379 private static final long serialVersionUID = 1L;
\r
381 setAlignmentX(LEFT_ALIGNMENT);
\r
382 this.setSelectedItem(motor.getFuel());
\r
383 setMinimumSize(new Dimension(200, 20));
\r
384 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
385 addActionListener(new ActionListener(){
\r
387 public void actionPerformed(ActionEvent e) {
\r
388 motor.setFuel((Fuel)getSelectedItem());
\r
389 log.debug("FUEL CHANGED");
\r
394 l = new JLabel("Casing:");
\r
395 l.setAlignmentX(LEFT_ALIGNMENT);
\r
396 nameAndFuel.add(l);
\r
398 nameAndFuel.add(new ClassChooser<Chamber>(chamberTypes, motor.getChamber()) {
\r
399 private static final long serialVersionUID = 1L;
\r
401 setAlignmentX(LEFT_ALIGNMENT);
\r
402 setMinimumSize(new Dimension(200, 20));
\r
403 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
406 protected Chamber classSelected(Class<? extends Chamber> clazz, Chamber c) {
\r
409 motor.setChamber(c);
\r
411 motor.setChamber(clazz.newInstance());
\r
413 return motor.getChamber();
\r
414 } catch (InstantiationException e) {
\r
416 } catch (IllegalAccessException e) {
\r
426 l = new JLabel("Delay:");
\r
427 l.setAlignmentX(LEFT_ALIGNMENT);
\r
428 nameAndFuel.add(l);
\r
429 nameAndFuel.add(new JTextField(Double.toString(motor.getEjectionDelay().doubleValue(SI.SECOND))) {
\r
430 private static final long serialVersionUID = 1L;
\r
432 setAlignmentX(LEFT_ALIGNMENT);
\r
433 setMinimumSize(new Dimension(200, 20));
\r
434 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
435 final JTextField t = this;
\r
436 addFocusListener(new FocusListener() {
\r
439 public void focusLost(FocusEvent e) {
\r
441 String n = t.getText();
\r
442 double d = Double.parseDouble(n);
\r
443 Amount<Duration> delay = Amount.valueOf(d, SI.SECOND);
\r
444 if ( delay != motor.getEjectionDelay() ){
\r
445 motor.setEjectionDelay(delay);
\r
447 } catch ( Exception ex ){
\r
449 setText(Double.toString(motor.getEjectionDelay().doubleValue(SI.SECOND)));
\r
454 public void focusGained(FocusEvent e) {
\r
464 nameAndFuel.add(Box.createVerticalGlue());
\r
465 parts.add(nameAndFuel);
\r
467 casing = new JPanel();
\r
468 casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));
\r
469 l = new JLabel("Casing:");
\r
470 l.setAlignmentX(LEFT_ALIGNMENT);
\r
474 nozzle = new JPanel();
\r
475 nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));
\r
476 l = new JLabel("Nozzle:");
\r
477 l.setAlignmentX(LEFT_ALIGNMENT);
\r
481 motor.addPropertyChangeListener(new PropertyChangeListener() {
\r
483 public void propertyChange(PropertyChangeEvent arg0) {
\r
485 setResizeWeight(.5);
\r
486 setDividerLocation(.5);
\r
494 public void componentHidden(ComponentEvent arg0) {
\r
499 public void componentMoved(ComponentEvent arg0) {
\r
504 public void componentResized(ComponentEvent arg0) {
\r
505 setResizeWeight(.5);
\r
506 setDividerLocation(.5);
\r
510 public void componentShown(ComponentEvent arg0) {
\r
518 public Motor getMotor() {
\r
523 private void setMotor(Motor m) {
\r
525 motor.removePropertyChangeListener(this);
\r
527 motor.addPropertyChangeListener(this);
\r
528 if (grainEditor != null)
\r
529 remove(grainEditor);
\r
530 while (tabs.getTabCount() > 1)
\r
531 tabs.removeTabAt(1);
\r
532 tabs.add(new CaseEditor(), CASING_TAB);
\r
533 tabs.add(new GrainEditor(motor.getGrain()), GRAIN_TAB);
\r
534 tabs.add(bt = new BurnTab(), BURN_TAB);
\r
537 private static int idx;
\r
538 public static Motor defaultMotor() {
\r
539 Motor m = new Motor();
\r
540 m.setName("New Motor " + ++idx);
\r
542 m.setFuel(FuelResolver.getFuel(new URI("motorsim:KNDX")));
\r
543 } catch (Exception e) {
\r
544 throw new Error(e);
\r
547 CylindricalChamber c = new CylindricalChamber();
\r
548 c.setLength(Amount.valueOf(420, SI.MILLIMETER));
\r
549 c.setID(Amount.valueOf(70, SI.MILLIMETER));
\r
550 c.setOD(Amount.valueOf(72, SI.MILLIMETER));
\r
553 CoredCylindricalGrain g = new CoredCylindricalGrain();
\r
555 g.setLength(Amount.valueOf(100, SI.MILLIMETER));
\r
556 g.setOD(Amount.valueOf(62, SI.MILLIMETER));
\r
557 g.setID(Amount.valueOf(20, SI.MILLIMETER));
\r
558 } catch (PropertyVetoException v) {
\r
559 throw new Error(v);
\r
562 MultiGrain mg = new MultiGrain(g, 4);
\r
563 mg.setSpacing(Amount.valueOf(6, SI.MILLIMETER));
\r
566 ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();
\r
567 n.setThroatDiameter(Amount.valueOf(14.089, SI.MILLIMETER));
\r
568 n.setExitDiameter(Amount.valueOf(44.55, SI.MILLIMETER));
\r
569 n.setEfficiency(.85);
\r
575 public void focusOnObject(Object o) {
\r
576 if (o instanceof Grain)
\r
577 tabs.setSelectedIndex(GRAIN_TAB);
\r
578 if (o instanceof Chamber || o instanceof Nozzle)
\r
579 tabs.setSelectedIndex(CASING_TAB);
\r
582 public void addBurnWatcher(BurnWatcher bw) {
\r
583 burnWatchers.add(bw);
\r
587 public void showAsWindow() {
\r
588 JFrame f = new JFrame();
\r
589 f.setSize(1024, 768);
\r
590 f.setContentPane(this);
\r
591 f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
\r
592 f.setVisible(true);
\r
595 public static void main(String args[]) throws Exception {
\r
597 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
\r
598 } catch (Exception e1) {
\r
599 e1.printStackTrace();
\r
601 Vector<Fuel> ff = new Vector<Fuel>();
\r
602 ff.add(new KNSU());
\r
603 //new MotorEditor(defaultMotor(), ff).showAsWindow();
\r
606 public void propertyChange(PropertyChangeEvent evt) {
\r
607 // Dont re-burn for a name change!
\r
608 if (!evt.getPropertyName().equals("Name")){
\r
611 for (BurnWatcher bw : burnWatchers)
\r
612 bw.replace(burn, burn);
\r