<classpathentry kind="src" path="src"/>\r
<classpathentry kind="src" path="test"/>\r
<classpathentry kind="src" path="motors"/>\r
+ <classpathentry kind="src" path="gui"/>\r
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
<classpathentry kind="lib" path="jscience.jar"/>\r
<classpathentry kind="lib" path="jfreechart-1.0.12.jar"/>\r
--- /dev/null
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+public class MotorSim {
+
+ public static void main(String args[]) throws Exception {
+
+ try {
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty(
+ "com.apple.mrj.application.apple.menu.about.name",
+ "MotorSim");
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ SwingUtilities.invokeLater(new Runnable(){
+ @Override
+ public void run() {
+ new com.billkuker.rocketry.motorsim.visual.workbench.MotorWorkbench().setVisible(true);
+ }
+ });
+
+ }
+
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Duration;\r
+import javax.measure.quantity.Force;\r
+import javax.measure.quantity.Pressure;\r
+import javax.measure.quantity.Velocity;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Burn.Interval;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+\r
+public class BurnPanel extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Burn burn;\r
+ Chart<Duration, Pressure> pressure;\r
+ Chart<Duration, Force> thrust;\r
+ Chart<Pressure, Velocity> burnRate;\r
+ GrainPanel grain;\r
+ Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
+ \r
+ public BurnPanel(Burn b){\r
+ super( new BorderLayout() );\r
+ burn = b;\r
+ \r
+ try {\r
+ pressure = new Chart<Duration, Pressure>(\r
+ SI.SECOND,\r
+ SI.MEGA(SI.PASCAL),\r
+ b,\r
+ "pressure");\r
+ pressure.setDomain(burn.getData().keySet());\r
+ \r
+ thrust = new Chart<Duration, Force>(\r
+ SI.SECOND,\r
+ SI.NEWTON,\r
+ b,\r
+ "thrust");\r
+ thrust.setDomain(burn.getData().keySet());\r
+ \r
+ burnRate = new Chart<Pressure, Velocity>(\r
+ SI.MEGA(SI.PASCAL),\r
+ SI.METERS_PER_SECOND,\r
+ burn.getMotor().getFuel(),\r
+ "burnRate");\r
+ burnRate.setDomain(\r
+ burnRate.new IntervalDomain(\r
+ Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
+ Amount.valueOf(11, SI.MEGA(SI.PASCAL)),\r
+ 20\r
+ ));\r
+ \r
+ \r
+ JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
+ tp.setDividerLocation(.5);\r
+ tp.setResizeWeight(.5);\r
+ \r
+ grain = new GrainPanel(burn.getMotor().getGrain()){\r
+ private static final long serialVersionUID = 1L;\r
+ @Override protected void addComponents(java.awt.Component crossSection, java.awt.Component slider, java.awt.Component label, java.awt.Component area, java.awt.Component volume) {\r
+ JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, crossSection, area); \r
+ add(h, BorderLayout.CENTER);\r
+ h.resetToPreferredSizes();\r
+ };\r
+ };\r
+ \r
+ JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
+ grains.setDividerLocation(.5);\r
+ grains.setResizeWeight(.5);\r
+ \r
+ JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
+ Dimension minimumSize = new Dimension(800, 200);\r
+ grains.setMinimumSize(minimumSize);\r
+ tp.setMinimumSize(minimumSize);\r
+ main.setDividerLocation(.5);\r
+ main.setResizeWeight(.5);\r
+ \r
+ add( main, BorderLayout.CENTER );\r
+ \r
+ add( new SL(), BorderLayout.SOUTH);\r
+ \r
+ \r
+\r
+ Amount<RocketScience.Impulse> ns = Amount.valueOf(0, RocketScience.NEWTON_SECOND);\r
+ \r
+ Amount<Duration> thrustTime = Amount.valueOf(0, SI.SECOND);\r
+ Amount<Force> maxThrust = Amount.valueOf(0, SI.NEWTON);\r
+ Amount<Pressure> maxPressure = Amount.valueOf(0, SI.MEGA(SI.PASCAL));\r
+\r
+ for( Interval i: burn.getData().values() ){\r
+ ns = ns.plus(i.dt.times(i.thrust));\r
+ if ( i.thrust.isGreaterThan(Amount.valueOf(0.01, SI.NEWTON))){\r
+ thrustTime = thrustTime.plus(i.dt);\r
+ }\r
+ if ( i.thrust.isGreaterThan(maxThrust))\r
+ maxThrust = i.thrust;\r
+ if ( i.chamberPressure.isGreaterThan(maxPressure))\r
+ maxPressure = i.chamberPressure;\r
+ }\r
+ \r
+ Amount<Force> averageThrust = Amount.valueOf(0, SI.NEWTON);\r
+ if ( thrustTime.isGreaterThan(Amount.valueOf(0, SI.SECOND)))\r
+ averageThrust = ns.divide(thrustTime).to(SI.NEWTON);\r
+\r
+ float cnf = (float)(Math.log(ns.doubleValue(RocketScience.NEWTON_SECOND)/1.25) / Math.log(2));\r
+ int cn = (int)cnf;\r
+ float fraction = cnf - cn;\r
+ int percent = (int)(100 * fraction);\r
+ char cl = (char)((int)'A' + cn);\r
+\r
+ \r
+ Amount<Duration> isp = ns.divide(\r
+ b.getMotor().getGrain().volume(Amount.valueOf(0, SI.MILLIMETER))\r
+ .times(b.getMotor().getFuel().getIdealDensity().times(b.getMotor().getFuel().getDensityRatio()))\r
+ ).to(SI.METERS_PER_SECOND).divide(Amount.valueOf(9.81, SI.METERS_PER_SQUARE_SECOND)).to(SI.SECOND);\r
+ \r
+ JPanel text = new JPanel(new GridLayout(2,5));\r
+\r
+ text.add(new JLabel("Rating"));\r
+ text.add(new JLabel("Total Impulse"));\r
+ text.add(new JLabel("ISP"));\r
+ text.add(new JLabel("Max Thrust"));\r
+ text.add(new JLabel("Average Thust"));\r
+ text.add(new JLabel("Max Pressure"));\r
+ \r
+ text.add(new JLabel(percent + "% " + new String(new char[]{cl}) + "-" +Math.round(averageThrust.doubleValue(SI.NEWTON))));\r
+ text.add(new JLabel(RocketScience.approx(ns)));\r
+ text.add(new JLabel(RocketScience.approx(isp))); \r
+ text.add(new JLabel(RocketScience.approx(maxThrust)));\r
+ text.add(new JLabel(RocketScience.approx(averageThrust)));\r
+ text.add(new JLabel(RocketScience.approx(maxPressure)));\r
+ \r
+ add(text, BorderLayout.NORTH);\r
+ \r
+ \r
+ } catch (NoSuchMethodException e){\r
+ throw new Error(e);\r
+ }\r
+ \r
+\r
+ }\r
+ \r
+ private class SL extends JSlider implements ChangeListener{\r
+ private static final long serialVersionUID = 1L;\r
+ private static final int STEPS = 80;\r
+ public SL(){\r
+ addChangeListener(this);\r
+ setMinimum(0);\r
+ setMaximum(STEPS);\r
+ setValue(0);\r
+ }\r
+ \r
+ public void stateChanged(ChangeEvent e) {\r
+ double t = ((SL)e.getSource()).getValue();\r
+ displayedTime = burn.burnTime().divide(STEPS).times(t);\r
+ \r
+ //Find the nearest key in the data set\r
+ displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
+ \r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ \r
+ pressure.mark(displayedTime);\r
+ thrust.mark(displayedTime);\r
+ \r
+ grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
+ \r
+ burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
+ \r
+ \r
+ /*\r
+ double r = ((SL)e.getSource()).getValue();\r
+ displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+ area.mark(displayedRegression);\r
+ volume.mark(displayedRegression);\r
+ if ( xc != null )\r
+ xc.repaint();\r
+ */\r
+ }\r
+ }\r
+ \r
+ public void showAsWindow(){\r
+ JFrame f = new JFrame();\r
+ f.setTitle(burn.getMotor().getName());\r
+ f.setSize(1280,720);\r
+ f.setLocation(0, 0);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.lang.reflect.Method;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.ThreadFactory;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Quantity;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+import javax.measure.unit.Unit;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.SwingUtilities;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jfree.chart.ChartFactory;\r
+import org.jfree.chart.ChartPanel;\r
+import org.jfree.chart.JFreeChart;\r
+import org.jfree.chart.plot.Marker;\r
+import org.jfree.chart.plot.PlotOrientation;\r
+import org.jfree.chart.plot.ValueMarker;\r
+import org.jfree.data.xy.XYSeries;\r
+import org.jfree.data.xy.XYSeriesCollection;\r
+import org.jfree.ui.RectangleInsets;\r
+import org.jfree.ui.TextAnchor;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
+\r
+public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private static Logger log = Logger.getLogger(Burn.class);\r
+\r
+ private static ThreadFactory tf = new ThreadFactory() {\r
+ public Thread newThread(Runnable r) {\r
+ Thread t = new Thread(r);\r
+ t.setDaemon(true);\r
+ return t;\r
+ }\r
+ };\r
+ private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
+ private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
+ private volatile boolean stop = false;\r
+\r
+ public class IntervalDomain implements Iterable<Amount<X>> {\r
+\r
+ Amount<X> low, high, delta;\r
+ int steps = 100;\r
+\r
+ public IntervalDomain(Amount<X> low, Amount<X> high) {\r
+ this.low = low;\r
+ this.high = high;\r
+ delta = high.minus(low).divide(steps);\r
+ }\r
+\r
+ public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {\r
+ this.steps = steps;\r
+ this.low = low;\r
+ this.high = high;\r
+ delta = high.minus(low).divide(steps);\r
+ }\r
+\r
+ public Iterator<Amount<X>> iterator() {\r
+ return new Iterator<Amount<X>>() {\r
+ Amount<X> current = low;\r
+\r
+ public boolean hasNext() {\r
+ return current.isLessThan(high.plus(delta));\r
+ }\r
+\r
+ public Amount<X> next() {\r
+ Amount<X> ret = current;\r
+ current = current.plus(delta);\r
+ return ret;\r
+ }\r
+\r
+ public final void remove() {\r
+ throw new UnsupportedOperationException(\r
+ "Chart domain iterators are not modifiable.");\r
+ }\r
+ };\r
+ }\r
+\r
+ }\r
+\r
+ XYSeriesCollection dataset = new XYSeriesCollection();\r
+ JFreeChart chart;\r
+\r
+ Unit<X> xUnit;\r
+ Unit<Y> yUnit;\r
+\r
+ Object source;\r
+ Method f;\r
+\r
+ public Chart(Unit<X> xUnit, Unit<Y> yUnit, Object source, String method)\r
+ throws NoSuchMethodException {\r
+ super(new BorderLayout());\r
+ f = source.getClass().getMethod(method, Amount.class);\r
+\r
+ this.source = source;\r
+\r
+\r
+ this.xUnit = RocketScience.UnitPreference.getUnitPreference()\r
+ .getPreferredUnit(xUnit);\r
+ this.yUnit = RocketScience.UnitPreference.getUnitPreference()\r
+ .getPreferredUnit(yUnit);\r
+\r
+ chart = ChartFactory.createXYLineChart(method.substring(0, 1)\r
+ .toUpperCase()\r
+ + method.substring(1), // Title\r
+ this.xUnit.toString(), // x-axis Label\r
+ this.yUnit.toString(), // y-axis Label\r
+ dataset, PlotOrientation.VERTICAL, // Plot Orientation\r
+ false, // Show Legend\r
+ true, // Use tool tips\r
+ false // Configure chart to generate URLs?\r
+ );\r
+ add(new ChartPanel(chart));\r
+ }\r
+\r
+ private Marker marker;\r
+\r
+ public void mark(Amount<X> m) {\r
+ if (marker != null)\r
+ chart.getXYPlot().removeDomainMarker(marker);\r
+ if (m != null) {\r
+ marker = new ValueMarker(m.doubleValue(xUnit));\r
+ marker.setPaint(Color.blue);\r
+ marker.setAlpha(0.8f);\r
+ \r
+ Amount<Y> val = getNear(m);\r
+ if ( val != null )\r
+ marker.setLabel(RocketScience.approx(val));\r
+ \r
+ marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
+ marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
+ \r
+ marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
+ chart.getXYPlot().addDomainMarker(marker);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Get the Y value at or near a given X\r
+ * For display use only!\r
+ * \r
+ * @param ax\r
+ * @return\r
+ */\r
+ private Amount<Y> getNear(final Amount<X> ax){\r
+ if ( dataset.getSeriesCount() != 1 )\r
+ return null;\r
+ final XYSeries s = dataset.getSeries(0);\r
+ final double x = ax.doubleValue(xUnit);\r
+ int idx = s.getItemCount() / 2;\r
+ int delta = s.getItemCount() / 4;\r
+ while(true){\r
+ if ( s.getX(idx).doubleValue() < x ){\r
+ idx += delta;\r
+ } else {\r
+ idx -= delta;\r
+ }\r
+ delta = delta / 2;\r
+ if ( delta < 1 ){\r
+ int idxL = idx-1;\r
+ int idxH = idx;\r
+ final double lowerX = s.getX(idxL).doubleValue();\r
+ final double higherX = s.getX(idxH).doubleValue();\r
+ final double sampleXDiff = higherX - lowerX;\r
+ final double xDiff = x - lowerX;\r
+ final double dist = xDiff / sampleXDiff;\r
+ final double lowerY = s.getY(idxL).doubleValue();\r
+ final double higherY = s.getY(idxH).doubleValue();\r
+ final double y = lowerY + dist * (higherY - lowerY);\r
+ \r
+ return Amount.valueOf( y, yUnit);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void setDomain(final Iterable<Amount<X>> d) {\r
+ stop = true;\r
+ fill(d, 100);\r
+ fast.submit(new Thread() {\r
+ public void run() {\r
+ if (!stop)\r
+ fill(d, 10);\r
+ slow.submit(new Thread() {\r
+ public void run() {\r
+ if (!stop)\r
+ fill(d, 1);\r
+ }\r
+ });\r
+ }\r
+ });\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
+ log.debug(f.getName() + " " + skip + " Start");\r
+ stop = false;\r
+ int sz = 0;\r
+ if (d instanceof Collection) {\r
+ sz = ((Collection<Amount<X>>) d).size();\r
+ int sk2 = sz / 200;\r
+ if (skip < sk2)\r
+ skip = sk2;\r
+ }\r
+ // series.clear();\r
+ int cnt = 0;\r
+\r
+ final XYSeries newSeries = new XYSeries(f.getName());\r
+ try {\r
+ Amount<X> last = null;\r
+ for (Amount<X> ax : d) {\r
+ if (stop) {\r
+ log.debug(f.getName() + " " + skip + " Abort");\r
+ return;\r
+ }\r
+ last = ax;\r
+ if (cnt % skip == 0) {\r
+ Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
+ newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
+ }\r
+ cnt++;\r
+ }\r
+ Amount<Y> y = (Amount<Y>) f.invoke(source, last);\r
+ newSeries.add(last.doubleValue(xUnit), y.doubleValue(yUnit));\r
+ SwingUtilities.invokeLater(new Thread() {\r
+ @Override\r
+ public void run() {\r
+ dataset.removeAllSeries();\r
+ dataset.addSeries(newSeries);\r
+ log.debug(f.getName() + " Replaced");\r
+ }\r
+ });\r
+ } catch (IllegalArgumentException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (IllegalAccessException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (InvocationTargetException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ log.debug(f.getName() + " " + skip + " Done");\r
+ }\r
+\r
+ public void show() {\r
+ new JFrame() {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ setContentPane(Chart.this);\r
+ setSize(640, 480);\r
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+ }\r
+ }.setVisible(true);\r
+ }\r
+\r
+ public static void main(String args[]) throws Exception {\r
+ CoredCylindricalGrain g = new CoredCylindricalGrain();\r
+ g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+ g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
+ g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
+\r
+ Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");\r
+\r
+ c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
+ .webThickness()));\r
+\r
+ c.show();\r
+\r
+ Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");\r
+\r
+ v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
+ .webThickness()));\r
+\r
+ v.show();\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.Component;\r
+import java.awt.Dimension;\r
+import java.beans.IntrospectionException;\r
+import java.beans.Introspector;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyDescriptor;\r
+import java.beans.PropertyEditorManager;\r
+import java.beans.PropertyEditorSupport;\r
+import java.text.DecimalFormat;\r
+import java.text.NumberFormat;\r
+import java.util.Vector;\r
+\r
+import javax.measure.unit.SI;\r
+import javax.measure.unit.Unit;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JTable;\r
+import javax.swing.JTextField;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.table.TableCellRenderer;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.l2fprod.common.propertysheet.PropertySheetPanel;\r
+\r
+public class Editor extends PropertySheetPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private static final NumberFormat nf = new DecimalFormat("##########.###");\r
+ private static final Logger log = Logger.getLogger(Editor.class);\r
+\r
+ private Object obj;\r
+\r
+ @SuppressWarnings("deprecation")\r
+ public Editor(Object o) {\r
+ obj = o;\r
+\r
+ PropertyEditorManager.registerEditor(Amount.class,\r
+ AmountPropertyEditor.class);\r
+ \r
+ setToolBarVisible(false);\r
+ //setMinimumSize(new Dimension(150,200));\r
+ \r
+ getRendererRegistry().registerRenderer(Amount.class, AmountRenderer.class);\r
+\r
+ // Build the list of properties we want it to edit\r
+ //final PropertySheetPanel ps = new PropertySheetPanel();\r
+ PropertyDescriptor props[];\r
+ try {\r
+ props = Introspector.getBeanInfo(obj.getClass())\r
+ .getPropertyDescriptors();\r
+ } catch (IntrospectionException e) {\r
+ throw new Error(e);\r
+ }\r
+ Vector<PropertyDescriptor> v = new Vector<PropertyDescriptor>();\r
+ for (int i = 0; i < props.length; i++) {\r
+ if (props[i].getName().equals("class"))\r
+ continue;\r
+ v.add(props[i]);\r
+ }\r
+ setProperties(v.toArray(new PropertyDescriptor[v.size()]));\r
+\r
+ readFromObject(obj);\r
+ \r
+ getTable().setRowHeight(22);\r
+ \r
+ setMinimumSize(new Dimension(\r
+ getTable().getPreferredSize().width,\r
+ getTable().getPreferredSize().height + 10));\r
+\r
+ addPropertySheetChangeListener(new PropertyChangeListener() {\r
+\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ // When something changes just update the\r
+ // object, I want the changes to be immediate.\r
+ try {\r
+ log.debug("Writing properties to object.");\r
+ writeToObject(obj);\r
+ } catch (Exception v) {\r
+ // TODO\r
+ v.printStackTrace();\r
+ java.awt.Toolkit.getDefaultToolkit().beep();\r
+ } finally {\r
+ readFromObject(obj);\r
+ }\r
+ }\r
+\r
+ });\r
+ }\r
+\r
+ public void showAsWindow() {\r
+ JFrame f = new JFrame();\r
+ f.setTitle(obj.getClass().getName());\r
+ f.setSize(600, 400);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+ \r
+ public static void main(String args[]){\r
+ CylindricalChamber o = new CylindricalChamber();\r
+ o.setLength(Amount.valueOf(100.5, SI.MILLIMETER));\r
+ o.setID(Amount.valueOf(30, SI.MILLIMETER));\r
+ Editor e = new Editor(o);\r
+ e.showAsWindow();\r
+ }\r
+ \r
+ public static class AmountRenderer implements TableCellRenderer {\r
+ @Override\r
+ public Component getTableCellRendererComponent(JTable table,\r
+ Object value, boolean isSelected, boolean hasFocus, int row,\r
+ int column) {\r
+ Amount a = (Amount)value;\r
+ return new JLabel(nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit() );\r
+ }\r
+ \r
+ }\r
+\r
+ public static class AmountPropertyEditor extends PropertyEditorSupport {\r
+ JTextField editor = new JTextField();\r
+ Unit<?> oldUnit;\r
+\r
+ @Override\r
+ public boolean supportsCustomEditor() {\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public String getAsText() {\r
+ return editor.getText();\r
+ }\r
+\r
+ @Override\r
+ public Object getValue() {\r
+ String text = editor.getText().trim();\r
+\r
+ // Trying to determine if the value is integer or\r
+ // has a decimal part will prevent the uncertainty\r
+ // term from appearing when user types an exact value\r
+ try {\r
+ try {\r
+ return Amount.valueOf(Integer.parseInt(text), oldUnit);\r
+ } catch (NumberFormatException e) {\r
+\r
+ }\r
+ Amount<?> a = Amount.valueOf(Double.parseDouble(text), oldUnit);\r
+ return a;\r
+ } catch (NumberFormatException e) {\r
+ // Storing the old unit allows you to type 10 into a field\r
+ // that says 20 mm and get 10 mm, so you dont have to\r
+ // type the unit if they havn't changed.\r
+ \r
+ //Amount wants a leading 0\r
+ if (text.startsWith(".")){\r
+ text = "0" + text;\r
+ }\r
+ \r
+ Amount<?> a = Amount.valueOf(text);\r
+ oldUnit = a.getUnit();\r
+ return a;\r
+ }\r
+\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public void setValue(Object o) {\r
+ Amount a = (Amount) o;\r
+ oldUnit = a.getUnit();\r
+\r
+ String text;\r
+ //Leave off the fractional part if it is not relevant\r
+ if (a.isExact())\r
+ text = a.getExactValue() + " " + a.getUnit();\r
+ else\r
+ text = nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit();\r
+\r
+ setAsText(text);\r
+ }\r
+\r
+ @Override\r
+ public void setAsText(String text) throws IllegalArgumentException {\r
+ editor.setText(text);\r
+ };\r
+\r
+ @Override\r
+ public Component getCustomEditor() {\r
+ return editor;\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;
+
+import javax.measure.quantity.Pressure;
+import javax.measure.quantity.Velocity;
+import javax.measure.unit.SI;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Fuel;
+
+public class FuelPanel extends JSplitPane {
+ private static final long serialVersionUID = 1L;
+
+ public FuelPanel(Fuel f) {
+ super(JSplitPane.HORIZONTAL_SPLIT);
+ setName("Fuel");
+ Chart<Pressure, Velocity> burnRate;
+ try {
+ burnRate = new Chart<Pressure, Velocity>(SI.MEGA(SI.PASCAL),
+ SI.METERS_PER_SECOND, f, "burnRate");
+ } catch (NoSuchMethodException e) {
+ throw new Error(e);
+ }
+ burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(0, SI
+ .MEGA(SI.PASCAL)), Amount.valueOf(11, SI.MEGA(SI.PASCAL)), 20));
+
+ final JPanel p = new JPanel();
+ p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+
+ p.add(new Editor(f));
+ try {
+ p.add(new Editor(f.getCombustionProduct()));
+ } catch (Exception e) {
+
+ }
+
+ setLeftComponent(p);
+ setRightComponent(burnRate);
+ }
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Dimension;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.geom.AffineTransform;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+\r
+public class GrainPanel extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
+ private JLabel l = new JLabel();\r
+ private Chart<Length,Area> area;\r
+ Chart<Length, Volume> volume;\r
+ private XC xc;\r
+ private Grain grain;\r
+ \r
+ public GrainPanel(Grain g){\r
+ super(new BorderLayout());\r
+\r
+ grain = g;\r
+ \r
+ if ( g instanceof ChangeListening.Subject ){\r
+ ((ChangeListening.Subject)g).addPropertyChangeListener(new PropertyChangeListener(){\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ repaint();\r
+ area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ }\r
+ });\r
+ }\r
+\r
+\r
+ try {\r
+\r
+ area = new Chart<Length, Area>(\r
+ SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(2).asType(Area.class),\r
+ grain,\r
+ "surfaceArea");\r
+ area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ \r
+ volume = new Chart<Length, Volume>(\r
+ SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(3).asType(Volume.class),\r
+ grain,\r
+ "volume");\r
+ volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+\r
+ area.setMaximumSize(new Dimension(200,100));\r
+ volume.setMaximumSize(new Dimension(200,100));\r
+ \r
+\r
+ } catch (ClassCastException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (NoSuchMethodException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ \r
+ addComponents(\r
+ xc = new XC(grain),\r
+ new SL(),\r
+ l,\r
+ area,\r
+ volume\r
+ );\r
+ }\r
+ \r
+ protected void addComponents(\r
+ Component crossSection,\r
+ Component slider,\r
+ Component label,\r
+ Component area,\r
+ Component volume\r
+ ){\r
+ \r
+ JSplitPane v = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r
+ JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\r
+ \r
+ JPanel graphics = new JPanel(new BorderLayout());\r
+ graphics.add(crossSection, BorderLayout.CENTER);\r
+ graphics.add(label, BorderLayout.NORTH);\r
+ graphics.add(slider, BorderLayout.SOUTH);\r
+ \r
+ v.setTopComponent(h);\r
+ v.setBottomComponent(area);\r
+ h.setLeftComponent(graphics);\r
+ h.setRightComponent(volume);\r
+ add(v);\r
+ \r
+ h.resetToPreferredSizes();\r
+ v.resetToPreferredSizes();\r
+\r
+ }\r
+ \r
+ public void setDisplayedRegression( Amount<Length> r ){\r
+ displayedRegression = r;\r
+ \r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+ \r
+ area.mark(displayedRegression);\r
+ volume.mark(displayedRegression);\r
+ if ( xc != null )\r
+ xc.repaint();\r
+ }\r
+ \r
+ private class XC extends JPanel{\r
+ private static final long serialVersionUID = 1L;\r
+ Grain grain;\r
+ public XC(Grain g){\r
+ grain = g;\r
+ java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = unburnt.getBounds();\r
+ double max = bounds.getWidth();\r
+ if ( bounds.getHeight() > max )\r
+ max = bounds.getHeight();\r
+ int w = (int)(bounds.getWidth() * 200.0 / max);\r
+ if ( w < 40 )\r
+ w = 40;\r
+ \r
+ Dimension sz = new Dimension(240+w, 250);\r
+ setMinimumSize(sz);\r
+ setPreferredSize(sz);\r
+ setMaximumSize(sz);\r
+ }\r
+ public void paint(Graphics g){\r
+ super.paint(g);\r
+ Graphics2D g2d = (Graphics2D)g;\r
+ g2d.translate(10, 30);\r
+ /*\r
+ grain.draw(g2d, displayedRegression );\r
+ */\r
+ \r
+ {\r
+ AffineTransform t = g2d.getTransform();\r
+ java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = unburnt.getBounds();\r
+ g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
+ g2d.translate(-bounds.getX(), -bounds.getY());\r
+ \r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
+ g2d.setColor(Color.RED);\r
+ g2d.fill(burning);\r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
+ g2d.setColor(Color.GRAY);\r
+ g2d.fill(left);\r
+ //Draw the outline of the unburnt grain\r
+ g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));\r
+ g2d.setColor(Color.BLACK);\r
+ g2d.draw(unburnt);\r
+ //untranslate\r
+ g2d.setTransform(t);\r
+ }\r
+ {\r
+ AffineTransform t = g2d.getTransform();\r
+ java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = unburnt.getBounds();\r
+ g2d.translate(220, 0);\r
+ \r
+ double max = bounds.getWidth();\r
+ if ( bounds.getHeight() > max )\r
+ max = bounds.getHeight();\r
+ \r
+ g2d.scale(200 / max, 200 / max);\r
+ g2d.translate(-bounds.getX(), -bounds.getY());\r
+ \r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
+ g2d.setColor(Color.RED);\r
+ g2d.fill(burning);\r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
+ g2d.setColor(Color.GRAY);\r
+ g2d.fill(left);\r
+ //Draw the outline of the unburnt grain\r
+ g2d.setColor(Color.BLACK);\r
+ g2d.draw(unburnt);\r
+ //untranslate\r
+ g2d.setTransform(t);\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ private class SL extends JSlider implements ChangeListener{\r
+ private static final long serialVersionUID = 1L;\r
+ private static final int STEPS = 60;\r
+ public SL(){\r
+ addChangeListener(this);\r
+ setMinimum(0);\r
+ setMaximum(STEPS);\r
+ setValue(0);\r
+ }\r
+ \r
+ public void stateChanged(ChangeEvent e) {\r
+ double r = ((SL)e.getSource()).getValue();\r
+\r
+ setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
+ }\r
+ }\r
+ \r
+ public void showAsWindow(){\r
+ JFrame f = new JFrame();\r
+ f.setTitle(grain.getClass().getName());\r
+ f.setSize(1024,600);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.WindowConstants;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Chamber;\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.billkuker.rocketry.motorsim.Nozzle;\r
+\r
+public class HardwarePanel extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Nozzle nozzle;\r
+ private Chamber chamber;\r
+ \r
+ public HardwarePanel(Nozzle n, Chamber c){\r
+ nozzle = n;\r
+ chamber = c;\r
+ if ( n instanceof ChangeListening.Subject ){\r
+ ((ChangeListening.Subject)n).addPropertyChangeListener(new PropertyChangeListener(){\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ repaint();\r
+ }\r
+ });\r
+ }\r
+ if ( c instanceof ChangeListening.Subject ){\r
+ ((ChangeListening.Subject)c).addPropertyChangeListener(new PropertyChangeListener(){\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ repaint();\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ public void paint(Graphics g){\r
+ super.paint(g);\r
+ Graphics2D g2d = (Graphics2D)g;\r
+ g2d.translate(10, 10);\r
+\r
+ g2d.setColor(Color.black);\r
+ \r
+ Shape c = chamber.chamberShape();\r
+ \r
+ Shape n = nozzle.nozzleShape(((CylindricalChamber)chamber).getID());\r
+ \r
+ Rectangle cb = c.getBounds();\r
+ Rectangle nb = n.getBounds();\r
+ double w, h;\r
+ w = Math.max(cb.getWidth(), nb.getWidth());\r
+ h = cb.getHeight() + nb.getHeight();\r
+ \r
+ double mw, mh;\r
+ mw = getHeight() - 10;\r
+ mh = getWidth() - 10;\r
+ \r
+ double sw, sh, s;\r
+ sw = mw / w;\r
+ sh = mh / h;\r
+ s = Math.min(sw, sh);\r
+ \r
+ g2d.rotate(-Math.PI / 2);\r
+ \r
+ g2d.translate(0, -cb.getY() - 5);\r
+ g2d.scale(s, s);\r
+ g2d.translate(-(getHeight()/(s*2)), 0);\r
+ \r
+ g2d.setStroke(new BasicStroke(1));\r
+ g2d.draw( c );\r
+ g2d.translate(0, cb.getHeight());\r
+ \r
+ g2d.draw(n);\r
+ }\r
+ \r
+ public void showAsWindow(){\r
+ JFrame f = new JFrame();\r
+ f.setSize(220,250);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+ \r
+ public static void main(String args[]) throws Exception{\r
+ ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
+ CylindricalChamber c = new CylindricalChamber();\r
+ n.setThroatDiameter(Amount.valueOf(10, SI.MILLIMETER));\r
+ n.setExitDiameter(Amount.valueOf(20, SI.MILLIMETER));\r
+ //new Editor(n).showAsWindow();\r
+ new HardwarePanel(n,c).showAsWindow();\r
+ }\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import com.billkuker.rocketry.motorsim.Burn;
+
+public interface BurnWatcher {
+ public void replace( Burn oldBurn, Burn newBurn);
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.FocusEvent;\r
+import java.awt.event.FocusListener;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyVetoException;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Vector;\r
+\r
+import javax.measure.quantity.Length;\r
+import javax.measure.unit.SI;\r
+import javax.swing.Box;\r
+import javax.swing.BoxLayout;\r
+import javax.swing.ComboBoxModel;\r
+import javax.swing.JButton;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JProgressBar;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JTabbedPane;\r
+import javax.swing.JTextArea;\r
+import javax.swing.JTextField;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.UIManager;\r
+import javax.swing.WindowConstants;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\r
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Chamber;\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.billkuker.rocketry.motorsim.Fuel;\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.Motor;\r
+import com.billkuker.rocketry.motorsim.Nozzle;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
+import com.billkuker.rocketry.motorsim.grain.CSlot;\r
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
+import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
+import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
+import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
+import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
+import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
+import com.billkuker.rocketry.motorsim.io.MotorIO;\r
+import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
+import com.billkuker.rocketry.motorsim.visual.Editor;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
+import com.billkuker.rocketry.motorsim.visual.HardwarePanel;\r
+\r
+public class MotorEditor extends JTabbedPane implements PropertyChangeListener {\r
+ private static final long serialVersionUID = 1L;\r
+ private static Logger log = Logger.getLogger(MotorEditor.class);\r
+ RSyntaxTextArea text = new RSyntaxTextArea();\r
+ Motor motor;\r
+ GrainEditor grainEditor;\r
+ BurnTab bt;\r
+ Burn burn;\r
+\r
+ private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
+ private ComboBoxModel availableFuels;\r
+\r
+ //private static final int XML_TAB = 0;\r
+ private static final int CASING_TAB = 0;\r
+ private static final int GRAIN_TAB = 1;\r
+ private static final int BURN_TAB = 2;\r
+\r
+ @SuppressWarnings("unchecked")\r
+ private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,\r
+ Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };\r
+\r
+ private abstract class Chooser<T> extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Class<? extends T>[] types;\r
+ private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
+\r
+ public Chooser(T initial, Class<? extends T>... ts) {\r
+ types = ts;\r
+ if ( initial != null )\r
+ old.put((Class<? extends T>)initial.getClass(), initial);\r
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
+ for (final Class<? extends T> c : types) {\r
+ JButton b = new JButton(c.getSimpleName());\r
+ add(b);\r
+ b.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ try {\r
+ T val = old.get(c);\r
+ if ( val == null ){\r
+ System.err.println("CREATED NEW =========================");\r
+ val = c.newInstance();\r
+ old.put(c, val);\r
+ }\r
+ choiceMade(val);\r
+ } catch (InstantiationException e1) {\r
+ e1.printStackTrace();\r
+ } catch (IllegalAccessException e1) {\r
+ e1.printStackTrace();\r
+ }\r
+ }\r
+ });\r
+ }\r
+ }\r
+\r
+ protected abstract void choiceMade(T o);\r
+ }\r
+\r
+ private class BurnTab extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Thread currentThread;\r
+ \r
+ public BurnTab() {\r
+ setLayout(new BorderLayout());\r
+ setName("Simulation Results");\r
+ reBurn();\r
+ }\r
+ \r
+ private class BurnCanceled extends RuntimeException{\r
+ private static final long serialVersionUID = 1L;\r
+ };\r
+\r
+ public void reBurn() {\r
+ removeAll();\r
+ currentThread = new Thread() {\r
+ public void run() {\r
+ final Thread me = this;\r
+ final JProgressBar bar = new JProgressBar(0, 100);\r
+ add(bar, BorderLayout.NORTH);\r
+ final JLabel progress = new JLabel();\r
+ add(progress, BorderLayout.CENTER);\r
+ try {\r
+ final Burn b = new Burn(motor,\r
+ new Burn.BurnProgressListener() {\r
+ @Override\r
+ public void setProgress(float f) {\r
+ int pct = (int)(f*100);\r
+ bar.setValue(pct);\r
+ Amount<Length> web = motor.getGrain().webThickness();\r
+ Amount<Length> remaining = web.times(1.0 - f);\r
+ \r
+ progress.setText("Progress: " + pct + "% (" + RocketScience.approx(remaining) + " web thickness remaining)");\r
+ if ( currentThread != me ){\r
+ throw new BurnCanceled();\r
+ }\r
+ }\r
+ });\r
+\r
+ final BurnPanel bp = new BurnPanel(b);\r
+ SwingUtilities.invokeLater(new Thread() {\r
+ public void run() {\r
+ remove(bar);\r
+ add(bp, BorderLayout.CENTER);\r
+\r
+ for (BurnWatcher bw : burnWatchers)\r
+ bw.replace(burn, b);\r
+ burn = b;\r
+\r
+ revalidate();\r
+ }\r
+ });\r
+ } catch (BurnCanceled c){\r
+ log.info("Burn Canceled!");\r
+ } catch (Exception e) {\r
+ remove(bar);\r
+ JTextArea t = new JTextArea(e.getMessage());\r
+ t.setEditable(false);\r
+ add(t);\r
+ }\r
+ }\r
+ };\r
+ currentThread.start();\r
+ }\r
+ }\r
+\r
+ private class GrainEditor extends JSplitPane {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public GrainEditor(final Grain g) {\r
+ super(JSplitPane.HORIZONTAL_SPLIT);\r
+ setName("Grain Geometry");\r
+ setRightComponent(new GrainPanel(g));\r
+ if (g instanceof Grain.Composite) {\r
+ final JPanel p = new JPanel();\r
+ p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
+ p.add(new Editor(g));\r
+ for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
+ final int grainEditorIndex = p.getComponentCount() + 1;\r
+ p.add(new Chooser<Grain>(gg, grainTypes) {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ protected void choiceMade(Grain ng) {\r
+ if (g instanceof MultiGrain) {\r
+ ((MultiGrain) g).setGrain(ng);\r
+ p.remove(grainEditorIndex);\r
+ p.add(new Editor(ng), grainEditorIndex);\r
+ p.remove(0);\r
+ p.add(new Editor(g), 0);\r
+ }\r
+ }\r
+ });\r
+ p.add(new Editor(gg));\r
+ if (gg instanceof ChangeListening.Subject) {\r
+ ((ChangeListening.Subject) gg)\r
+ .addPropertyChangeListener(MotorEditor.this);\r
+ }\r
+ }\r
+ setLeftComponent(p);\r
+ } else {\r
+ setLeftComponent(new Editor(g));\r
+ }\r
+ // setDividerLocation(.25);\r
+ // setResizeWeight(.25);\r
+ if (g instanceof ChangeListening.Subject) {\r
+ ((ChangeListening.Subject) g)\r
+ .addPropertyChangeListener(MotorEditor.this);\r
+ }\r
+ }\r
+ }\r
+\r
+ private class CaseEditor extends JSplitPane {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public CaseEditor(Nozzle n, Chamber c) {\r
+ super(JSplitPane.VERTICAL_SPLIT);\r
+ setName("General Parameters");\r
+ \r
+ JPanel parts = new JPanel();\r
+ parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));\r
+ setTopComponent(parts);\r
+ setBottomComponent(new HardwarePanel(n, c));\r
+ \r
+ JPanel nameAndFuel = new JPanel();\r
+ nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));\r
+\r
+ nameAndFuel.add(new JLabel("Name:"));\r
+ nameAndFuel.add(new JTextField(motor.getName()) {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ setMinimumSize(new Dimension(200, 20));\r
+ setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
+ final JTextField t = this;\r
+ addFocusListener(new FocusListener() {\r
+\r
+ @Override\r
+ public void focusLost(FocusEvent e) {\r
+ String n = t.getText();\r
+ if (!"".equals(n) && !n.equals(motor.getName())) {\r
+ motor.setName(n);\r
+ } else {\r
+ t.setText(motor.getName());\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void focusGained(FocusEvent e) {\r
+\r
+ }\r
+ });\r
+\r
+ }\r
+ });\r
+ nameAndFuel.add(new JLabel("Fuel:"));\r
+ nameAndFuel.add( new JComboBox(availableFuels){{\r
+ setMinimumSize(new Dimension(200, 20));\r
+ setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
+ addActionListener(new ActionListener(){\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ motor.setFuel((Fuel)getSelectedItem());\r
+ System.out.println("FUEL CHANGED");\r
+ }});\r
+ }});\r
+ nameAndFuel.add(Box.createVerticalGlue());\r
+ parts.add(nameAndFuel);\r
+ \r
+ JPanel casing = new JPanel();\r
+ casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));\r
+ casing.add(new JLabel("Casing:"));\r
+ casing.add(new Editor(c));\r
+ parts.add(casing);\r
+ \r
+ JPanel nozzle = new JPanel();\r
+ nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));\r
+ nozzle.add(new JLabel("Nozzle:"));\r
+ nozzle.add(new Editor(n));\r
+ parts.add(nozzle);\r
+\r
+ if (n instanceof ChangeListening.Subject) {\r
+ ((ChangeListening.Subject) n)\r
+ .addPropertyChangeListener(MotorEditor.this);\r
+ }\r
+ if (c instanceof ChangeListening.Subject) {\r
+ ((ChangeListening.Subject) c)\r
+ .addPropertyChangeListener(MotorEditor.this);\r
+ }\r
+ }\r
+ }\r
+\r
+ {\r
+ text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);\r
+\r
+ }\r
+\r
+ public MotorEditor(Motor m, ComboBoxModel fuels) {\r
+ super(JTabbedPane.BOTTOM);\r
+ this.availableFuels = fuels;\r
+ text.setName("XML");\r
+ text.setEditable(false);\r
+ //add(text, XML_TAB);\r
+ setMotor(m, true);\r
+ }\r
+\r
+ public Motor getMotor() {\r
+ return motor;\r
+ }\r
+\r
+ private void reText() {\r
+ try {\r
+ text.setText(MotorIO.writeMotor(motor));\r
+ } catch (IOException e) {\r
+ throw new Error(e);\r
+ }\r
+ }\r
+\r
+ private void setMotor(Motor m, boolean retext) {\r
+ if (motor != null)\r
+ motor.removePropertyChangeListener(this);\r
+ motor = m;\r
+ motor.addPropertyChangeListener(this);\r
+ if (retext)\r
+ reText();\r
+ if (grainEditor != null)\r
+ remove(grainEditor);\r
+ while (getTabCount() > 1)\r
+ removeTabAt(1);\r
+ add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);\r
+ add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
+ add(bt = new BurnTab(), BURN_TAB);\r
+ }\r
+\r
+ @Deprecated\r
+ public static Motor defaultMotor() {\r
+ Motor m = new Motor();\r
+ m.setName("Example Motor");\r
+ m.setFuel(new KNSU());\r
+\r
+ CylindricalChamber c = new CylindricalChamber();\r
+ c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
+ c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
+ m.setChamber(c);\r
+\r
+ CoredCylindricalGrain g = new CoredCylindricalGrain();\r
+ try {\r
+ g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+ g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
+ g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
+ } catch (PropertyVetoException v) {\r
+ throw new Error(v);\r
+ }\r
+\r
+ m.setGrain(new MultiGrain(g, 2));\r
+\r
+ ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
+ n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
+ n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
+ n.setEfficiency(.85);\r
+ m.setNozzle(n);\r
+\r
+ return m;\r
+ }\r
+\r
+ public void focusOnObject(Object o) {\r
+ if (o instanceof Grain)\r
+ setSelectedIndex(GRAIN_TAB);\r
+ if (o instanceof Chamber || o instanceof Nozzle)\r
+ setSelectedIndex(CASING_TAB);\r
+ }\r
+\r
+ public void addBurnWatcher(BurnWatcher bw) {\r
+ burnWatchers.add(bw);\r
+ }\r
+\r
+ @Deprecated\r
+ public void showAsWindow() {\r
+ JFrame f = new JFrame();\r
+ f.setSize(1024, 768);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+\r
+ public static void main(String args[]) throws Exception {\r
+ try {\r
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
+ } catch (Exception e1) {\r
+ e1.printStackTrace();\r
+ }\r
+ Vector<Fuel> ff = new Vector<Fuel>();\r
+ ff.add(new KNSU());\r
+ //new MotorEditor(defaultMotor(), ff).showAsWindow();\r
+ }\r
+\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ reText();\r
+ // Dont re-burn for a name change!\r
+ if (!evt.getPropertyName().equals("Name")){\r
+ bt.reBurn();\r
+ } else {\r
+ for (BurnWatcher bw : burnWatchers)\r
+ bw.replace(burn, burn);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.FileDialog;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.io.File;\r
+import java.util.HashMap;\r
+import java.util.Vector;\r
+\r
+import javax.swing.ButtonGroup;\r
+import javax.swing.DefaultComboBoxModel;\r
+import javax.swing.JFrame;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuBar;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JRadioButtonMenuItem;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSeparator;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JTabbedPane;\r
+import javax.swing.JTree;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.TreeSelectionEvent;\r
+import javax.swing.event.TreeSelectionListener;\r
+import javax.swing.tree.DefaultMutableTreeNode;\r
+import javax.swing.tree.TreePath;\r
+import javax.swing.tree.TreeSelectionModel;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Fuel;\r
+import com.billkuker.rocketry.motorsim.Motor;\r
+import com.billkuker.rocketry.motorsim.RocketScience.UnitPreference;\r
+import com.billkuker.rocketry.motorsim.fuel.KNDX;\r
+import com.billkuker.rocketry.motorsim.fuel.KNER;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSB;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
+import com.billkuker.rocketry.motorsim.io.ENGExporter;\r
+import com.billkuker.rocketry.motorsim.io.MotorIO;\r
+import com.billkuker.rocketry.motorsim.visual.FuelPanel;\r
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelEditNode;\r
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;\r
+\r
+public class MotorWorkbench extends JFrame implements TreeSelectionListener {\r
+ private static final long serialVersionUID = 1L;\r
+ \r
+ @SuppressWarnings("unchecked")\r
+ private Class[] fuelTypes = { KNSB.class, KNSU.class, KNER.class,\r
+ KNDX.class };\r
+ \r
+ private JPanel top;\r
+ private JSplitPane split;\r
+ private JTree tree;\r
+ private JTabbedPane motors;\r
+ private WorkbenchTreeModel tm;\r
+ private MultiBurnChart mb;\r
+ private JFrame allBurns;\r
+\r
+ private HashMap<MotorEditor, File> e2f = new HashMap<MotorEditor, File>();\r
+ private HashMap<File, MotorEditor> f2e = new HashMap<File, MotorEditor>();\r
+\r
+ private HashMap<Motor, MotorEditor> m2e = new HashMap<Motor, MotorEditor>();\r
+ \r
+ private DefaultComboBoxModel fuels = new DefaultComboBoxModel();\r
+ \r
+ public MotorWorkbench() {\r
+ setTitle("MotorSim 1.0 RC1");\r
+ addMenu();\r
+ setSize(1024, 768);\r
+ top = new JPanel(new BorderLayout());\r
+ setContentPane(top);\r
+ \r
+ mb = new MultiBurnChart();\r
+ allBurns = new JFrame();\r
+ allBurns.setTitle("All Burns");\r
+ allBurns.setSize(800, 600);\r
+ setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);\r
+ allBurns.add(mb);\r
+\r
+ motors = new JTabbedPane();\r
+\r
+ tree = new JTree(tm = new WorkbenchTreeModel());\r
+ tree.setCellRenderer(new WorkbenchTreeCellRenderer());\r
+ tree.getSelectionModel().setSelectionMode(\r
+ TreeSelectionModel.SINGLE_TREE_SELECTION);\r
+ tree.setMinimumSize(new Dimension(200, 100));\r
+\r
+ // Listen for when the selection changes.\r
+ tree.addTreeSelectionListener(this);\r
+\r
+ split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(\r
+ tree), motors);\r
+ split.setDividerLocation(200);\r
+ split.setResizeWeight(0);\r
+ split.resetToPreferredSizes();\r
+ split.revalidate();\r
+ \r
+ top.add(split, BorderLayout.CENTER);\r
+ \r
+ for ( Class<Fuel> f : fuelTypes){\r
+ try {\r
+ addFuel(f.newInstance());\r
+ } catch (InstantiationException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (IllegalAccessException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);\r
+ setVisible(true);\r
+\r
+ }\r
+\r
+ private void addMenu() {\r
+\r
+ setJMenuBar(new JMenuBar() {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ {\r
+ add(new JMenu("File") {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ {\r
+ add(new JMenuItem("New Motor") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+ addMotor(MotorEditor.defaultMotor(),\r
+ null);\r
+ }\r
+ });\r
+\r
+ }\r
+ });\r
+ add(new JMenuItem("Open...") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+\r
+ final FileDialog fd = new FileDialog(MotorWorkbench.this, "Open Motor", FileDialog.LOAD);\r
+ fd.setVisible(true);\r
+ if ( fd.getFile() != null ) {\r
+ File file = new File(fd.getDirectory() + fd.getFile());\r
+ if (f2e.get(file) != null) {\r
+ motors.setSelectedComponent(f2e\r
+ .get(file));\r
+ return;\r
+ }\r
+ try {\r
+ Motor m = MotorIO\r
+ .readMotor(file);\r
+ addMotor(m, file);\r
+ } catch (Exception e) {\r
+ JOptionPane.showMessageDialog(\r
+ MotorWorkbench.this, e\r
+ .getMessage());\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+ });\r
+ \r
+ add(new JSeparator());\r
+ \r
+ add(new JMenuItem("Close Motor") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent ev) {\r
+ MotorEditor e = (MotorEditor) motors\r
+ .getSelectedComponent();\r
+ tm.removeMotor(e.getMotor());\r
+ motors.remove(e);\r
+ f2e.remove(e2f.get(e));\r
+ e2f.remove(e);\r
+ m2e.remove(e.getMotor());\r
+ mb.removeBurn(e.burn);\r
+ }\r
+ });\r
+ }\r
+ });\r
+ \r
+ add(new JMenuItem("Save Motor") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ MotorEditor m = (MotorEditor) motors\r
+ .getSelectedComponent();\r
+ File f = e2f.get(m);\r
+ if (f != null)\r
+ save(m.getMotor(), f);\r
+\r
+ }\r
+ });\r
+ }\r
+ });\r
+ add(new JMenuItem("Save Motor As...") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+\r
+ final FileDialog fd = new FileDialog(MotorWorkbench.this, "Save Motor As", FileDialog.SAVE);\r
+ fd.setVisible(true);\r
+ if (fd.getFile() != null ) {\r
+ File file = new File(fd.getDirectory() + fd.getFile());\r
+ MotorEditor m = (MotorEditor) motors\r
+ .getSelectedComponent();\r
+ try {\r
+ save(m.getMotor(), file);\r
+ e2f.put(m, file);\r
+ f2e.put(file, m);\r
+ motors.setTitleAt(motors\r
+ .getSelectedIndex(),\r
+ file.getName());\r
+ // TODO Set tab title\r
+ } catch (Exception e) {\r
+ JOptionPane.showMessageDialog(\r
+ MotorWorkbench.this, e\r
+ .getMessage());\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+ });\r
+ \r
+\r
+ add(new JSeparator());\r
+ add(new JMenuItem("New Fuel") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+ newFuel();\r
+ }\r
+ });\r
+\r
+ }\r
+ });\r
+ add(new JMenuItem("Save Fuel") {});\r
+ add(new JSeparator());\r
+ add(new JMenuItem("Export .ENG"){\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+\r
+ final FileDialog fd = new FileDialog(MotorWorkbench.this, "Export .ENG File", FileDialog.SAVE);\r
+ fd.setFile("motorsim.eng");\r
+ fd.setVisible(true);\r
+ if (fd.getFile() != null ) {\r
+ File file = new File(fd.getDirectory() + fd.getFile());\r
+ Vector<Burn> bb = new Vector<Burn>();\r
+ for( MotorEditor me : m2e.values() )\r
+ bb.add(me.burn);\r
+ try{\r
+ ENGExporter.export(bb, file);\r
+ } catch ( Exception e ){\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+ });\r
+ }\r
+ });\r
+ add(new JMenu("Settings") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ ButtonGroup units = new ButtonGroup();\r
+ JRadioButtonMenuItem sci = new JRadioButtonMenuItem(\r
+ "SI");\r
+ JRadioButtonMenuItem nonsci = new JRadioButtonMenuItem(\r
+ "NonSI");\r
+ units.add(sci);\r
+ units.add(nonsci);\r
+ sci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.SI);\r
+ nonsci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.NONSI);\r
+ sci.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent arg0) {\r
+ UnitPreference\r
+ .setUnitPreference(UnitPreference.SI);\r
+ }\r
+ });\r
+ nonsci.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent arg0) {\r
+ UnitPreference\r
+ .setUnitPreference(UnitPreference.NONSI);\r
+ }\r
+ });\r
+ add(sci);\r
+ add(nonsci);\r
+ }\r
+ });\r
+ add(new JMenu("View") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ add(new JMenuItem("Show All Motors Graph") {\r
+ private static final long serialVersionUID = 1L;\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
+ allBurns.setVisible(true);\r
+ allBurns.toFront();\r
+ }\r
+ });\r
+ }\r
+ });\r
+ }\r
+ });\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void addFuel(Fuel f){\r
+ fuels.addElement(f);\r
+ FuelPanel fp = new FuelPanel(f);\r
+ FuelNode fn = tm.new FuelNode(fp, f);\r
+ tm.getFuels().add(fn);\r
+ tm.nodeStructureChanged(tm.getFuels());\r
+ motors.addTab(f.getName(), fp);\r
+ }\r
+ \r
+ private void newFuel(){\r
+ final SRFuelEditor ed = new SRFuelEditor();\r
+ fuels.addElement(ed.getFuel());\r
+ final FuelEditNode node = tm.new FuelEditNode(ed);\r
+ tm.getFuels().add(node);\r
+ tm.nodeStructureChanged(tm.getFuels());\r
+ motors.addTab(ed.getFuel().getName(), ed);\r
+ ed.getFuel().addPropertyChangeListener(new PropertyChangeListener(){\r
+ @Override\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ if ( evt.getPropertyName().equals("Name")){\r
+ for ( int i = 0; i < motors.getTabCount(); i++ ){\r
+ if ( motors.getComponent(i) == ed ){\r
+ motors.setTitleAt(i, ed.getFuel().getName());\r
+ tm.nodeChanged(node);\r
+ }\r
+ }\r
+ }\r
+ }});\r
+ }\r
+\r
+ private void save(Motor m, File f) {\r
+ try {\r
+ MotorIO.writeMotor(m, f);\r
+ } catch (Throwable t) {\r
+ JOptionPane.showMessageDialog(MotorWorkbench.this, t.getMessage());\r
+ }\r
+ }\r
+\r
+ public void addMotor(Motor m, File f) {\r
+ tm.addMotor(m);\r
+ MotorEditor e = new MotorEditor(m, fuels);\r
+ e.addBurnWatcher(mb);\r
+ String title;\r
+ if (f == null) {\r
+ title = "New Motor";\r
+ } else {\r
+ title = f.getName();\r
+ e2f.put(e, f);\r
+ f2e.put(f, e);\r
+ }\r
+ m2e.put(m, e);\r
+ motors.addTab(title, e);\r
+ }\r
+\r
+ @Override\r
+ public void valueChanged(TreeSelectionEvent e) { \r
+ if ( e.getPath().getLastPathComponent() instanceof FuelNode ){\r
+ FuelNode fen = ((FuelNode)e.getPath().getLastPathComponent());\r
+ motors.setSelectedComponent(fen.getUserObject()); \r
+ }\r
+ \r
+ Motor m = getMotor(e.getPath());\r
+ \r
+ if ( m == null )\r
+ return;\r
+\r
+ motors.setSelectedComponent(m2e.get(m));\r
+\r
+ if (e.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) {\r
+ Object o = ((DefaultMutableTreeNode) e.getPath()\r
+ .getLastPathComponent()).getUserObject();\r
+ m2e.get(m).focusOnObject(o);\r
+ }\r
+ \r
+\r
+ }\r
+\r
+ private Motor getMotor(TreePath p) {\r
+ if (p.getLastPathComponent() instanceof WorkbenchTreeModel.MotorNode) {\r
+ return ((WorkbenchTreeModel.MotorNode) p.getLastPathComponent())\r
+ .getUserObject();\r
+ } else if (p.getPath().length > 1)\r
+ return getMotor(p.getParentPath());\r
+ return null;\r
+ }\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.BorderLayout;
+import java.util.HashMap;
+
+import javax.measure.quantity.Duration;
+import javax.measure.quantity.Force;
+import javax.measure.unit.SI;
+import javax.measure.unit.Unit;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Burn;
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.RocketScience;
+
+public class MultiBurnChart extends JPanel implements BurnWatcher {
+ private static final long serialVersionUID = 1L;
+
+ private XYSeriesCollection dataset = new XYSeriesCollection();
+
+ private HashMap<Burn, XYSeries> burnToSeries = new HashMap<Burn, XYSeries>();
+ private Unit<Duration> time;
+ private Unit<Force> force;
+
+ @SuppressWarnings("unchecked")
+ public MultiBurnChart() {
+ this.setLayout(new BorderLayout());
+ time = RocketScience.UnitPreference.getUnitPreference()
+ .getPreferredUnit(SI.SECOND);
+ force = RocketScience.UnitPreference.getUnitPreference()
+ .getPreferredUnit(SI.NEWTON);
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "", // Title
+ time.toString(), // x-axis Label
+ force.toString(), // y-axis Label
+ dataset, PlotOrientation.VERTICAL, // Plot Orientation
+ true, // Show Legend
+ true, // Use tool tips
+ false // Configure chart to generate URLs?
+ );
+ add(new ChartPanel(chart));
+ }
+
+ public void addBurn(Burn b) {
+ XYSeries s = createSeries(b);
+ burnToSeries.put(b, s);
+ dataset.addSeries(s);
+ }
+
+ private XYSeries createSeries(Burn b) {
+ XYSeries s = new XYSeries(b.getMotor().getName());
+ for( Burn.Interval i : b.getData().values() ){
+ s.add(i.time.doubleValue(time), i.thrust.doubleValue(force));
+ }
+ return s;
+ }
+
+ public void removeBurn(Burn b) {
+ XYSeries s = burnToSeries.get(b);
+ if (s == null)
+ return;
+ dataset.removeSeries(s);
+ }
+
+ public static void main(String args[]) throws Exception{
+ MultiBurnChart c = new MultiBurnChart();
+
+ JFrame f = new JFrame();
+ f.setSize(1024, 768);
+ f.setContentPane(c);
+ f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ f.setVisible(true);
+
+ Motor m = MotorEditor.defaultMotor();
+ Burn b = new Burn(m);
+ c.addBurn(b);
+
+ m.setName("Motor2");
+ ((ConvergentDivergentNozzle)m.getNozzle()).setThroatDiameter(Amount.valueOf(3, SI.MILLIMETER));
+ c.addBurn(new Burn(m));
+
+ Thread.sleep(5000);
+
+ c.removeBurn(b);
+
+ }
+
+ @Override
+ public void replace(Burn oldBurn, Burn newBurn) {
+ removeBurn(oldBurn);
+ addBurn(newBurn);
+ }
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Collections;
+import java.util.Vector;
+
+import javax.measure.quantity.Pressure;
+import javax.measure.quantity.Velocity;
+import javax.measure.quantity.VolumetricDensity;
+import javax.measure.unit.SI;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.table.AbstractTableModel;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Fuel;
+import com.billkuker.rocketry.motorsim.RocketScience;
+import com.billkuker.rocketry.motorsim.fuel.EditableFuel.EditableCombustionProduct;
+import com.billkuker.rocketry.motorsim.fuel.PiecewiseSaintRobertFuel;
+import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel;
+import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel.Type;
+import com.billkuker.rocketry.motorsim.visual.Chart;
+import com.billkuker.rocketry.motorsim.visual.Editor;
+
+public class SRFuelEditor extends JSplitPane {
+ private static final NumberFormat nf = new DecimalFormat("##########.###");
+
+ Chart<Pressure, Velocity> burnRate;
+
+ private class Entry implements Comparable<Entry> {
+ Amount<Pressure> p = Amount.valueOf(0, RocketScience.UnitPreference.getUnitPreference().getPreferredUnit(RocketScience.PSI));
+ double a;
+ double n;
+
+ @Override
+ public int compareTo(Entry o) {
+ return p.compareTo(o.p);
+ }
+ }
+
+ public static class EditablePSRFuel extends PiecewiseSaintRobertFuel {
+
+ private Amount<VolumetricDensity> idealDensity = (Amount<VolumetricDensity>) Amount
+ .valueOf("1 g/mm^3");
+ private double combustionEfficiency = 1;
+ private double densityRatio = 1;
+ private EditableCombustionProduct cp;
+ private String name = "New Fuel";
+
+ public EditablePSRFuel(Type t) {
+ super(t);
+ cp = new EditableCombustionProduct();
+ }
+
+ public void clear(){
+ super.clear();
+ }
+
+ public void setType(Type t){
+ super.setType(t);
+ }
+
+ public void add(Amount<Pressure> p, final double _a, final double _n) {
+ super.add(p, _a, _n);
+
+ }
+
+ public Amount<VolumetricDensity> getIdealDensity() {
+ return idealDensity;
+ }
+
+ public void setIdealDensity(Amount<VolumetricDensity> idealDensity) {
+ this.idealDensity = idealDensity;
+ }
+
+ public double getCombustionEfficiency() {
+ return combustionEfficiency;
+ }
+
+ public void setCombustionEfficiency(double combustionEfficiency) {
+ this.combustionEfficiency = combustionEfficiency;
+ }
+
+ public double getDensityRatio() {
+ return densityRatio;
+ }
+
+ public void setDensityRatio(double densityRatio) {
+ this.densityRatio = densityRatio;
+ }
+
+ @Override
+ public CombustionProduct getCombustionProduct() {
+ return cp;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ }
+
+ final EditablePSRFuel f = new EditablePSRFuel(SaintRobertFuel.Type.SI);
+
+ private class TM extends AbstractTableModel {
+
+ @Override
+ public int getColumnCount() {
+ return 3;
+ }
+
+ @Override
+ public int getRowCount() {
+ return entries.size();
+ }
+
+ @Override
+ public String getColumnName(int col) {
+ switch (col) {
+ case 0:
+ return "Pressure";
+ case 1:
+ return "Coefficient (a)";
+ case 2:
+ return "Exponent (n)";
+ }
+ return null;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ Entry e = entries.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ //Format like 100 psi or 4.8 Mpa
+ return nf.format(e.p.doubleValue(e.p.getUnit())) + " " + e.p.getUnit();
+ case 1:
+ return e.a;
+ case 2:
+ return e.n;
+ }
+ return null;
+ }
+
+ public boolean isCellEditable(int row, int col) {
+ return true;
+ }
+
+ public void setValueAt(Object value, int row, int col) {
+ Entry e = entries.get(row);
+ try {
+ switch (col) {
+ case 0:
+ try {
+ e.p = (Amount<Pressure>) Amount.valueOf((String) value);
+ } catch ( Exception ee ){
+ double d = Double.parseDouble((String)value);
+ e.p = (Amount<Pressure>)Amount.valueOf(d, e.p.getUnit());
+ }
+ break;
+ case 1:
+ e.a = Double.valueOf((String) value);
+ break;
+ case 2:
+ e.n = Double.valueOf((String) value);
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ Collections.sort(entries);
+ fireTableDataChanged();
+ //f = new EditablePSRFuel(SaintRobertFuel.Type.NONSI);
+ f.clear();
+ for (Entry en : entries) {
+ f.add(en.p, en.a, en.n);
+ }
+ f.firePropertyChange(new PropertyChangeEvent(f,"entries", null, null));
+
+ update();
+
+ }
+
+ @Override
+ public void fireTableDataChanged() {
+ super.fireTableDataChanged();
+ }
+
+ };
+
+ public Fuel getFuel(){
+ return f;
+ }
+
+ private void update() {
+ SwingUtilities.invokeLater(new Runnable() {
+
+ @Override
+ public void run() {
+ editTop.setTopComponent(new Editor(f));
+ editTop.setBottomComponent(new Editor(f.getCombustionProduct()));
+ if (burnRate != null)
+ SRFuelEditor.this.remove(burnRate);
+ try {
+ burnRate = new Chart<Pressure, Velocity>(
+ SI.MEGA(SI.PASCAL), SI.MILLIMETER.divide(SI.SECOND)
+ .asType(Velocity.class), f, "burnRate");
+ } catch (NoSuchMethodException e) {
+ throw new Error(e);
+ }
+ burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(
+ 0, SI.MEGA(SI.PASCAL)), Amount.valueOf(11, SI
+ .MEGA(SI.PASCAL)), 50));
+ SRFuelEditor.this.setRightComponent(burnRate);
+ SRFuelEditor.this.revalidate();
+ }
+ });
+ }
+
+ private Vector<Entry> entries = new Vector<Entry>();
+
+ JSplitPane editParent;
+ JSplitPane editTop;
+ JSplitPane editBottom;
+ JPanel controls;
+
+ public SRFuelEditor() {
+ super(HORIZONTAL_SPLIT);
+ setResizeWeight(0);
+ setDividerLocation(.3);
+
+ final TM tm = new TM();
+
+ editParent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ editTop = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ editBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+
+ editParent.setTopComponent(editTop);
+ editParent.setBottomComponent(editBottom);
+
+ editTop.setTopComponent(new Editor(f));
+
+ JTable table = new JTable(tm);
+ JScrollPane scrollpane = new JScrollPane(table);
+ scrollpane.setMinimumSize(new Dimension(200, 200));
+ editBottom.setTopComponent(scrollpane);
+
+ setLeftComponent(editParent);
+
+ JButton add = new JButton("Add Data");
+ add.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ entries.add(new Entry());
+ tm.fireTableDataChanged();
+ }
+ });
+ controls = new JPanel();
+ controls.setPreferredSize(new Dimension(200, 50));
+ controls.setLayout(new FlowLayout());
+
+ controls.add(add);
+
+
+ final JRadioButton si, nonsi;
+ ButtonGroup type = new ButtonGroup();
+ JPanel radio = new JPanel();
+ radio.add(si = new JRadioButton("SI"));
+ radio.add(nonsi = new JRadioButton("NonSI"));
+ controls.add(radio);
+ type.add(si);
+ type.add(nonsi);
+
+ si.setSelected(true);
+
+ si.addChangeListener(new ChangeListener(){
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if ( si.isSelected() ){
+ System.err.println("SI");
+ f.setType(Type.SI);
+ } else {
+ System.err.println("NONSI");
+ f.setType(Type.NONSI);
+ }
+ update();
+ }});
+
+ editBottom.setBottomComponent(controls);
+
+
+ editParent.setDividerLocation(.5);
+ editTop.setDividerLocation(.5);
+ editBottom.setDividerLocation(.8);
+
+ editParent.resetToPreferredSizes();
+ revalidate();
+
+ update();
+ }
+
+ public static void main(String args[]) {
+ SRFuelEditor ed;
+ JFrame f = new JFrame();
+ f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ f.setContentPane(ed = new SRFuelEditor());
+ f.setSize(800, 600);
+ f.setVisible(true);
+
+ }
+
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.Validating;
+import com.billkuker.rocketry.motorsim.Validating.ValidationException;
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;
+
+public class WorkbenchTreeCellRenderer extends DefaultTreeCellRenderer {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, final Object value,
+ boolean sel, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+
+ String tip = null;
+ setTextNonSelectionColor(Color.black);
+ setTextSelectionColor(Color.white);
+
+ Object part = null;
+ if (value instanceof DefaultMutableTreeNode) {
+ part = ((DefaultMutableTreeNode) value).getUserObject();
+ }
+
+ if ( part instanceof Validating ){
+ try {
+ ((Validating)part).validate();
+ } catch (ValidationException e) {
+ setTextSelectionColor(Color.RED);
+ setTextNonSelectionColor(Color.RED);
+ setToolTipText(e.getMessage());
+ tip = e.getMessage();
+ }
+ }
+
+ super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
+ row, hasFocus);
+
+ if (part instanceof Motor) {
+ setText(((Motor) part).getName());
+ } else if ( value instanceof FuelNode ){
+ setText(((FuelNode)value).getFuel().getName());
+ } else if ( part instanceof String ) {
+ setText((String)part);
+ } else if ( part == null ) {
+ setText("");
+ } else {
+ setText(part.getClass().getSimpleName());
+ }
+ setToolTipText(tip);
+
+
+
+ return this;
+ }
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Component;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+
+import com.billkuker.rocketry.motorsim.ChangeListening;
+import com.billkuker.rocketry.motorsim.Fuel;
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.grain.MultiGrain;
+
+public class WorkbenchTreeModel extends DefaultTreeModel {
+
+ private static final long serialVersionUID = 1L;
+
+ //TreeNode root = new DefaultMutableTreeNode("Root");
+ DefaultMutableTreeNode motors = new DefaultMutableTreeNode("All Motors");
+ DefaultMutableTreeNode fuel = new DefaultMutableTreeNode("Fuels");
+
+ public class MultiGrainNode extends PartNode{
+ private static final long serialVersionUID = 1L;
+ public MultiGrainNode(MultiGrain part) {
+ super(part);
+ setAllowsChildren(true);
+ add(new PartNode(part.getGrain()));
+ }
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ if ( e.getPropertyName().equals("Grain")){
+ remove(0);
+ add(new PartNode(((MultiGrain)getUserObject()).getGrain()));
+ nodesChanged(this, new int[]{0});
+ }
+ super.propertyChange(e);
+ }
+ }
+
+ public class FuelNode extends DefaultMutableTreeNode{
+ private static final long serialVersionUID = 1L;
+ Fuel f;
+ public FuelNode(Component c, Fuel f){
+ super(c, false);
+ this.f = f;
+ }
+
+ @Override
+ public Component getUserObject(){
+ return (Component)super.getUserObject();
+ }
+
+ public Fuel getFuel(){
+ return f;
+ }
+ }
+
+ public class FuelEditNode extends FuelNode {
+ private static final long serialVersionUID = 1L;
+
+ public FuelEditNode(SRFuelEditor sr){
+ super(sr, sr.getFuel());
+ sr.getFuel().addPropertyChangeListener(new PropertyChangeListener(){
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ nodeChanged(FuelEditNode.this);
+ }});
+ }
+
+ @Override
+ public SRFuelEditor getUserObject(){
+ return (SRFuelEditor)super.getUserObject();
+ }
+
+ }
+
+ public class PartNode extends DefaultMutableTreeNode implements PropertyChangeListener {
+ private static final long serialVersionUID = 1L;
+
+ public PartNode(Object part) {
+ super(part, false);
+ if (part instanceof ChangeListening.Subject) {
+ ((ChangeListening.Subject) part).addPropertyChangeListener(this);
+ }
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ nodeChanged(this);
+ }
+
+ }
+
+ public class MotorNode extends PartNode implements PropertyChangeListener {
+ private static final long serialVersionUID = 1L;
+ Motor motor;
+ PartNode cn, nn, gn, fn;
+
+ public MotorNode(Motor m) {
+ super(m);
+ setAllowsChildren(true);
+ motor = m;
+ add( cn = new PartNode(m.getChamber()));
+ add( nn = new PartNode(m.getNozzle()));
+ if ( m.getGrain() instanceof MultiGrain ){
+ gn = new MultiGrainNode(((MultiGrain)m.getGrain()));
+ } else {
+ gn = new PartNode(m.getGrain());
+ }
+ add(gn);
+ if (m instanceof ChangeListening.Subject) {
+ ((ChangeListening.Subject) m).addPropertyChangeListener(this);
+ }
+ }
+
+ @Override
+ public Motor getUserObject(){
+ return (Motor)super.getUserObject();
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ nodeChanged(this);
+ super.propertyChange(e);
+ }
+
+ }
+
+ public WorkbenchTreeModel() {
+ super(new DefaultMutableTreeNode("Root"), true);
+ getRoot().add(motors);
+ getRoot().add(fuel);
+ }
+
+ @Override
+ public DefaultMutableTreeNode getRoot(){
+ return (DefaultMutableTreeNode)super.getRoot();
+ }
+
+ public DefaultMutableTreeNode getMotors(){
+ return motors;
+ }
+
+ public DefaultMutableTreeNode getFuels(){
+ return fuel;
+ }
+
+ public void addMotor(Motor m){
+ DefaultMutableTreeNode root = getRoot();
+ motors.add(new MotorNode(m));
+ nodesWereInserted(motors, new int[]{motors.getChildCount()-1});
+
+ }
+
+ @SuppressWarnings("unchecked")
+ public void removeMotor(Motor m){
+ Enumeration<TreeNode> e = motors.children();
+ while ( e.hasMoreElements() ){
+ TreeNode n = e.nextElement();
+ if ( n instanceof MotorNode ){
+ if ( ((MotorNode)n).getUserObject() == m ){
+ removeNodeFromParent((MotorNode)n);
+ }
+ }
+ }
+ }
+
+}
+++ /dev/null
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
-
-public class MotorSim {
-
- public static void main(String args[]) throws Exception {
-
- try {
- System.setProperty("apple.laf.useScreenMenuBar", "true");
- System.setProperty(
- "com.apple.mrj.application.apple.menu.about.name",
- "MotorSim");
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- SwingUtilities.invokeLater(new Runnable(){
- @Override
- public void run() {
- new com.billkuker.rocketry.motorsim.visual.workbench.MotorWorkbench().setVisible(true);
- }
- });
-
- }
-
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.GridLayout;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Duration;\r
-import javax.measure.quantity.Force;\r
-import javax.measure.quantity.Pressure;\r
-import javax.measure.quantity.Velocity;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Burn.Interval;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-\r
-public class BurnPanel extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Burn burn;\r
- Chart<Duration, Pressure> pressure;\r
- Chart<Duration, Force> thrust;\r
- Chart<Pressure, Velocity> burnRate;\r
- GrainPanel grain;\r
- Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
- \r
- public BurnPanel(Burn b){\r
- super( new BorderLayout() );\r
- burn = b;\r
- \r
- try {\r
- pressure = new Chart<Duration, Pressure>(\r
- SI.SECOND,\r
- SI.MEGA(SI.PASCAL),\r
- b,\r
- "pressure");\r
- pressure.setDomain(burn.getData().keySet());\r
- \r
- thrust = new Chart<Duration, Force>(\r
- SI.SECOND,\r
- SI.NEWTON,\r
- b,\r
- "thrust");\r
- thrust.setDomain(burn.getData().keySet());\r
- \r
- burnRate = new Chart<Pressure, Velocity>(\r
- SI.MEGA(SI.PASCAL),\r
- SI.METERS_PER_SECOND,\r
- burn.getMotor().getFuel(),\r
- "burnRate");\r
- burnRate.setDomain(\r
- burnRate.new IntervalDomain(\r
- Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
- Amount.valueOf(11, SI.MEGA(SI.PASCAL)),\r
- 20\r
- ));\r
- \r
- \r
- JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
- tp.setDividerLocation(.5);\r
- tp.setResizeWeight(.5);\r
- \r
- grain = new GrainPanel(burn.getMotor().getGrain()){\r
- private static final long serialVersionUID = 1L;\r
- @Override protected void addComponents(java.awt.Component crossSection, java.awt.Component slider, java.awt.Component label, java.awt.Component area, java.awt.Component volume) {\r
- JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, crossSection, area); \r
- add(h, BorderLayout.CENTER);\r
- h.resetToPreferredSizes();\r
- };\r
- };\r
- \r
- JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
- grains.setDividerLocation(.5);\r
- grains.setResizeWeight(.5);\r
- \r
- JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
- Dimension minimumSize = new Dimension(800, 200);\r
- grains.setMinimumSize(minimumSize);\r
- tp.setMinimumSize(minimumSize);\r
- main.setDividerLocation(.5);\r
- main.setResizeWeight(.5);\r
- \r
- add( main, BorderLayout.CENTER );\r
- \r
- add( new SL(), BorderLayout.SOUTH);\r
- \r
- \r
-\r
- Amount<RocketScience.Impulse> ns = Amount.valueOf(0, RocketScience.NEWTON_SECOND);\r
- \r
- Amount<Duration> thrustTime = Amount.valueOf(0, SI.SECOND);\r
- Amount<Force> maxThrust = Amount.valueOf(0, SI.NEWTON);\r
- Amount<Pressure> maxPressure = Amount.valueOf(0, SI.MEGA(SI.PASCAL));\r
-\r
- for( Interval i: burn.getData().values() ){\r
- ns = ns.plus(i.dt.times(i.thrust));\r
- if ( i.thrust.isGreaterThan(Amount.valueOf(0.01, SI.NEWTON))){\r
- thrustTime = thrustTime.plus(i.dt);\r
- }\r
- if ( i.thrust.isGreaterThan(maxThrust))\r
- maxThrust = i.thrust;\r
- if ( i.chamberPressure.isGreaterThan(maxPressure))\r
- maxPressure = i.chamberPressure;\r
- }\r
- \r
- Amount<Force> averageThrust = Amount.valueOf(0, SI.NEWTON);\r
- if ( thrustTime.isGreaterThan(Amount.valueOf(0, SI.SECOND)))\r
- averageThrust = ns.divide(thrustTime).to(SI.NEWTON);\r
-\r
- float cnf = (float)(Math.log(ns.doubleValue(RocketScience.NEWTON_SECOND)/1.25) / Math.log(2));\r
- int cn = (int)cnf;\r
- float fraction = cnf - cn;\r
- int percent = (int)(100 * fraction);\r
- char cl = (char)((int)'A' + cn);\r
-\r
- \r
- Amount<Duration> isp = ns.divide(\r
- b.getMotor().getGrain().volume(Amount.valueOf(0, SI.MILLIMETER))\r
- .times(b.getMotor().getFuel().getIdealDensity().times(b.getMotor().getFuel().getDensityRatio()))\r
- ).to(SI.METERS_PER_SECOND).divide(Amount.valueOf(9.81, SI.METERS_PER_SQUARE_SECOND)).to(SI.SECOND);\r
- \r
- JPanel text = new JPanel(new GridLayout(2,5));\r
-\r
- text.add(new JLabel("Rating"));\r
- text.add(new JLabel("Total Impulse"));\r
- text.add(new JLabel("ISP"));\r
- text.add(new JLabel("Max Thrust"));\r
- text.add(new JLabel("Average Thust"));\r
- text.add(new JLabel("Max Pressure"));\r
- \r
- text.add(new JLabel(percent + "% " + new String(new char[]{cl}) + "-" +Math.round(averageThrust.doubleValue(SI.NEWTON))));\r
- text.add(new JLabel(RocketScience.approx(ns)));\r
- text.add(new JLabel(RocketScience.approx(isp))); \r
- text.add(new JLabel(RocketScience.approx(maxThrust)));\r
- text.add(new JLabel(RocketScience.approx(averageThrust)));\r
- text.add(new JLabel(RocketScience.approx(maxPressure)));\r
- \r
- add(text, BorderLayout.NORTH);\r
- \r
- \r
- } catch (NoSuchMethodException e){\r
- throw new Error(e);\r
- }\r
- \r
-\r
- }\r
- \r
- private class SL extends JSlider implements ChangeListener{\r
- private static final long serialVersionUID = 1L;\r
- private static final int STEPS = 80;\r
- public SL(){\r
- addChangeListener(this);\r
- setMinimum(0);\r
- setMaximum(STEPS);\r
- setValue(0);\r
- }\r
- \r
- public void stateChanged(ChangeEvent e) {\r
- double t = ((SL)e.getSource()).getValue();\r
- displayedTime = burn.burnTime().divide(STEPS).times(t);\r
- \r
- //Find the nearest key in the data set\r
- displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
- \r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- \r
- pressure.mark(displayedTime);\r
- thrust.mark(displayedTime);\r
- \r
- grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
- \r
- burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
- \r
- \r
- /*\r
- double r = ((SL)e.getSource()).getValue();\r
- displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
- area.mark(displayedRegression);\r
- volume.mark(displayedRegression);\r
- if ( xc != null )\r
- xc.repaint();\r
- */\r
- }\r
- }\r
- \r
- public void showAsWindow(){\r
- JFrame f = new JFrame();\r
- f.setTitle(burn.getMotor().getName());\r
- f.setSize(1280,720);\r
- f.setLocation(0, 0);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Font;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.Collection;\r
-import java.util.Iterator;\r
-import java.util.concurrent.ExecutorService;\r
-import java.util.concurrent.Executors;\r
-import java.util.concurrent.ThreadFactory;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Quantity;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-import javax.measure.unit.Unit;\r
-import javax.swing.JFrame;\r
-import javax.swing.JPanel;\r
-import javax.swing.SwingUtilities;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.jfree.chart.ChartFactory;\r
-import org.jfree.chart.ChartPanel;\r
-import org.jfree.chart.JFreeChart;\r
-import org.jfree.chart.plot.Marker;\r
-import org.jfree.chart.plot.PlotOrientation;\r
-import org.jfree.chart.plot.ValueMarker;\r
-import org.jfree.data.xy.XYSeries;\r
-import org.jfree.data.xy.XYSeriesCollection;\r
-import org.jfree.ui.RectangleInsets;\r
-import org.jfree.ui.TextAnchor;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
-\r
-public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private static Logger log = Logger.getLogger(Burn.class);\r
-\r
- private static ThreadFactory tf = new ThreadFactory() {\r
- public Thread newThread(Runnable r) {\r
- Thread t = new Thread(r);\r
- t.setDaemon(true);\r
- return t;\r
- }\r
- };\r
- private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
- private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
- private volatile boolean stop = false;\r
-\r
- public class IntervalDomain implements Iterable<Amount<X>> {\r
-\r
- Amount<X> low, high, delta;\r
- int steps = 100;\r
-\r
- public IntervalDomain(Amount<X> low, Amount<X> high) {\r
- this.low = low;\r
- this.high = high;\r
- delta = high.minus(low).divide(steps);\r
- }\r
-\r
- public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {\r
- this.steps = steps;\r
- this.low = low;\r
- this.high = high;\r
- delta = high.minus(low).divide(steps);\r
- }\r
-\r
- public Iterator<Amount<X>> iterator() {\r
- return new Iterator<Amount<X>>() {\r
- Amount<X> current = low;\r
-\r
- public boolean hasNext() {\r
- return current.isLessThan(high.plus(delta));\r
- }\r
-\r
- public Amount<X> next() {\r
- Amount<X> ret = current;\r
- current = current.plus(delta);\r
- return ret;\r
- }\r
-\r
- public final void remove() {\r
- throw new UnsupportedOperationException(\r
- "Chart domain iterators are not modifiable.");\r
- }\r
- };\r
- }\r
-\r
- }\r
-\r
- XYSeriesCollection dataset = new XYSeriesCollection();\r
- JFreeChart chart;\r
-\r
- Unit<X> xUnit;\r
- Unit<Y> yUnit;\r
-\r
- Object source;\r
- Method f;\r
-\r
- public Chart(Unit<X> xUnit, Unit<Y> yUnit, Object source, String method)\r
- throws NoSuchMethodException {\r
- super(new BorderLayout());\r
- f = source.getClass().getMethod(method, Amount.class);\r
-\r
- this.source = source;\r
-\r
-\r
- this.xUnit = RocketScience.UnitPreference.getUnitPreference()\r
- .getPreferredUnit(xUnit);\r
- this.yUnit = RocketScience.UnitPreference.getUnitPreference()\r
- .getPreferredUnit(yUnit);\r
-\r
- chart = ChartFactory.createXYLineChart(method.substring(0, 1)\r
- .toUpperCase()\r
- + method.substring(1), // Title\r
- this.xUnit.toString(), // x-axis Label\r
- this.yUnit.toString(), // y-axis Label\r
- dataset, PlotOrientation.VERTICAL, // Plot Orientation\r
- false, // Show Legend\r
- true, // Use tool tips\r
- false // Configure chart to generate URLs?\r
- );\r
- add(new ChartPanel(chart));\r
- }\r
-\r
- private Marker marker;\r
-\r
- public void mark(Amount<X> m) {\r
- if (marker != null)\r
- chart.getXYPlot().removeDomainMarker(marker);\r
- if (m != null) {\r
- marker = new ValueMarker(m.doubleValue(xUnit));\r
- marker.setPaint(Color.blue);\r
- marker.setAlpha(0.8f);\r
- \r
- Amount<Y> val = getNear(m);\r
- if ( val != null )\r
- marker.setLabel(RocketScience.approx(val));\r
- \r
- marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
- marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
- \r
- marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
- chart.getXYPlot().addDomainMarker(marker);\r
- }\r
- }\r
- \r
- /**\r
- * Get the Y value at or near a given X\r
- * For display use only!\r
- * \r
- * @param ax\r
- * @return\r
- */\r
- private Amount<Y> getNear(final Amount<X> ax){\r
- if ( dataset.getSeriesCount() != 1 )\r
- return null;\r
- final XYSeries s = dataset.getSeries(0);\r
- final double x = ax.doubleValue(xUnit);\r
- int idx = s.getItemCount() / 2;\r
- int delta = s.getItemCount() / 4;\r
- while(true){\r
- if ( s.getX(idx).doubleValue() < x ){\r
- idx += delta;\r
- } else {\r
- idx -= delta;\r
- }\r
- delta = delta / 2;\r
- if ( delta < 1 ){\r
- int idxL = idx-1;\r
- int idxH = idx;\r
- final double lowerX = s.getX(idxL).doubleValue();\r
- final double higherX = s.getX(idxH).doubleValue();\r
- final double sampleXDiff = higherX - lowerX;\r
- final double xDiff = x - lowerX;\r
- final double dist = xDiff / sampleXDiff;\r
- final double lowerY = s.getY(idxL).doubleValue();\r
- final double higherY = s.getY(idxH).doubleValue();\r
- final double y = lowerY + dist * (higherY - lowerY);\r
- \r
- return Amount.valueOf( y, yUnit);\r
- }\r
- }\r
- }\r
-\r
- public void setDomain(final Iterable<Amount<X>> d) {\r
- stop = true;\r
- fill(d, 100);\r
- fast.submit(new Thread() {\r
- public void run() {\r
- if (!stop)\r
- fill(d, 10);\r
- slow.submit(new Thread() {\r
- public void run() {\r
- if (!stop)\r
- fill(d, 1);\r
- }\r
- });\r
- }\r
- });\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
- log.debug(f.getName() + " " + skip + " Start");\r
- stop = false;\r
- int sz = 0;\r
- if (d instanceof Collection) {\r
- sz = ((Collection<Amount<X>>) d).size();\r
- int sk2 = sz / 200;\r
- if (skip < sk2)\r
- skip = sk2;\r
- }\r
- // series.clear();\r
- int cnt = 0;\r
-\r
- final XYSeries newSeries = new XYSeries(f.getName());\r
- try {\r
- Amount<X> last = null;\r
- for (Amount<X> ax : d) {\r
- if (stop) {\r
- log.debug(f.getName() + " " + skip + " Abort");\r
- return;\r
- }\r
- last = ax;\r
- if (cnt % skip == 0) {\r
- Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
- newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
- }\r
- cnt++;\r
- }\r
- Amount<Y> y = (Amount<Y>) f.invoke(source, last);\r
- newSeries.add(last.doubleValue(xUnit), y.doubleValue(yUnit));\r
- SwingUtilities.invokeLater(new Thread() {\r
- @Override\r
- public void run() {\r
- dataset.removeAllSeries();\r
- dataset.addSeries(newSeries);\r
- log.debug(f.getName() + " Replaced");\r
- }\r
- });\r
- } catch (IllegalArgumentException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- } catch (IllegalAccessException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- } catch (InvocationTargetException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- }\r
- log.debug(f.getName() + " " + skip + " Done");\r
- }\r
-\r
- public void show() {\r
- new JFrame() {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- setContentPane(Chart.this);\r
- setSize(640, 480);\r
- setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
- }\r
- }.setVisible(true);\r
- }\r
-\r
- public static void main(String args[]) throws Exception {\r
- CoredCylindricalGrain g = new CoredCylindricalGrain();\r
- g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
- g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
- g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
-\r
- Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,\r
- SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");\r
-\r
- c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
- .webThickness()));\r
-\r
- c.show();\r
-\r
- Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,\r
- SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");\r
-\r
- v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
- .webThickness()));\r
-\r
- v.show();\r
- }\r
-\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.Component;\r
-import java.awt.Dimension;\r
-import java.beans.IntrospectionException;\r
-import java.beans.Introspector;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.beans.PropertyDescriptor;\r
-import java.beans.PropertyEditorManager;\r
-import java.beans.PropertyEditorSupport;\r
-import java.text.DecimalFormat;\r
-import java.text.NumberFormat;\r
-import java.util.Vector;\r
-\r
-import javax.measure.unit.SI;\r
-import javax.measure.unit.Unit;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JTable;\r
-import javax.swing.JTextField;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.table.TableCellRenderer;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.l2fprod.common.propertysheet.PropertySheetPanel;\r
-\r
-public class Editor extends PropertySheetPanel {\r
- private static final long serialVersionUID = 1L;\r
- private static final NumberFormat nf = new DecimalFormat("##########.###");\r
- private static final Logger log = Logger.getLogger(Editor.class);\r
-\r
- private Object obj;\r
-\r
- @SuppressWarnings("deprecation")\r
- public Editor(Object o) {\r
- obj = o;\r
-\r
- PropertyEditorManager.registerEditor(Amount.class,\r
- AmountPropertyEditor.class);\r
- \r
- setToolBarVisible(false);\r
- //setMinimumSize(new Dimension(150,200));\r
- \r
- getRendererRegistry().registerRenderer(Amount.class, AmountRenderer.class);\r
-\r
- // Build the list of properties we want it to edit\r
- //final PropertySheetPanel ps = new PropertySheetPanel();\r
- PropertyDescriptor props[];\r
- try {\r
- props = Introspector.getBeanInfo(obj.getClass())\r
- .getPropertyDescriptors();\r
- } catch (IntrospectionException e) {\r
- throw new Error(e);\r
- }\r
- Vector<PropertyDescriptor> v = new Vector<PropertyDescriptor>();\r
- for (int i = 0; i < props.length; i++) {\r
- if (props[i].getName().equals("class"))\r
- continue;\r
- v.add(props[i]);\r
- }\r
- setProperties(v.toArray(new PropertyDescriptor[v.size()]));\r
-\r
- readFromObject(obj);\r
- \r
- getTable().setRowHeight(22);\r
- \r
- setMinimumSize(new Dimension(\r
- getTable().getPreferredSize().width,\r
- getTable().getPreferredSize().height + 10));\r
-\r
- addPropertySheetChangeListener(new PropertyChangeListener() {\r
-\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- // When something changes just update the\r
- // object, I want the changes to be immediate.\r
- try {\r
- log.debug("Writing properties to object.");\r
- writeToObject(obj);\r
- } catch (Exception v) {\r
- // TODO\r
- v.printStackTrace();\r
- java.awt.Toolkit.getDefaultToolkit().beep();\r
- } finally {\r
- readFromObject(obj);\r
- }\r
- }\r
-\r
- });\r
- }\r
-\r
- public void showAsWindow() {\r
- JFrame f = new JFrame();\r
- f.setTitle(obj.getClass().getName());\r
- f.setSize(600, 400);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
- \r
- public static void main(String args[]){\r
- CylindricalChamber o = new CylindricalChamber();\r
- o.setLength(Amount.valueOf(100.5, SI.MILLIMETER));\r
- o.setID(Amount.valueOf(30, SI.MILLIMETER));\r
- Editor e = new Editor(o);\r
- e.showAsWindow();\r
- }\r
- \r
- public static class AmountRenderer implements TableCellRenderer {\r
- @Override\r
- public Component getTableCellRendererComponent(JTable table,\r
- Object value, boolean isSelected, boolean hasFocus, int row,\r
- int column) {\r
- Amount a = (Amount)value;\r
- return new JLabel(nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit() );\r
- }\r
- \r
- }\r
-\r
- public static class AmountPropertyEditor extends PropertyEditorSupport {\r
- JTextField editor = new JTextField();\r
- Unit<?> oldUnit;\r
-\r
- @Override\r
- public boolean supportsCustomEditor() {\r
- return true;\r
- }\r
-\r
- @Override\r
- public String getAsText() {\r
- return editor.getText();\r
- }\r
-\r
- @Override\r
- public Object getValue() {\r
- String text = editor.getText().trim();\r
-\r
- // Trying to determine if the value is integer or\r
- // has a decimal part will prevent the uncertainty\r
- // term from appearing when user types an exact value\r
- try {\r
- try {\r
- return Amount.valueOf(Integer.parseInt(text), oldUnit);\r
- } catch (NumberFormatException e) {\r
-\r
- }\r
- Amount<?> a = Amount.valueOf(Double.parseDouble(text), oldUnit);\r
- return a;\r
- } catch (NumberFormatException e) {\r
- // Storing the old unit allows you to type 10 into a field\r
- // that says 20 mm and get 10 mm, so you dont have to\r
- // type the unit if they havn't changed.\r
- \r
- //Amount wants a leading 0\r
- if (text.startsWith(".")){\r
- text = "0" + text;\r
- }\r
- \r
- Amount<?> a = Amount.valueOf(text);\r
- oldUnit = a.getUnit();\r
- return a;\r
- }\r
-\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public void setValue(Object o) {\r
- Amount a = (Amount) o;\r
- oldUnit = a.getUnit();\r
-\r
- String text;\r
- //Leave off the fractional part if it is not relevant\r
- if (a.isExact())\r
- text = a.getExactValue() + " " + a.getUnit();\r
- else\r
- text = nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit();\r
-\r
- setAsText(text);\r
- }\r
-\r
- @Override\r
- public void setAsText(String text) throws IllegalArgumentException {\r
- editor.setText(text);\r
- };\r
-\r
- @Override\r
- public Component getCustomEditor() {\r
- return editor;\r
- }\r
- }\r
-\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;
-
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Velocity;
-import javax.measure.unit.SI;
-import javax.swing.BoxLayout;
-import javax.swing.JPanel;
-import javax.swing.JSplitPane;
-
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Fuel;
-
-public class FuelPanel extends JSplitPane {
- private static final long serialVersionUID = 1L;
-
- public FuelPanel(Fuel f) {
- super(JSplitPane.HORIZONTAL_SPLIT);
- setName("Fuel");
- Chart<Pressure, Velocity> burnRate;
- try {
- burnRate = new Chart<Pressure, Velocity>(SI.MEGA(SI.PASCAL),
- SI.METERS_PER_SECOND, f, "burnRate");
- } catch (NoSuchMethodException e) {
- throw new Error(e);
- }
- burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(0, SI
- .MEGA(SI.PASCAL)), Amount.valueOf(11, SI.MEGA(SI.PASCAL)), 20));
-
- final JPanel p = new JPanel();
- p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
-
- p.add(new Editor(f));
- try {
- p.add(new Editor(f.getCombustionProduct()));
- } catch (Exception e) {
-
- }
-
- setLeftComponent(p);
- setRightComponent(burnRate);
- }
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Component;\r
-import java.awt.Dimension;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.geom.AffineTransform;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-\r
-public class GrainPanel extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
- private JLabel l = new JLabel();\r
- private Chart<Length,Area> area;\r
- Chart<Length, Volume> volume;\r
- private XC xc;\r
- private Grain grain;\r
- \r
- public GrainPanel(Grain g){\r
- super(new BorderLayout());\r
-\r
- grain = g;\r
- \r
- if ( g instanceof ChangeListening.Subject ){\r
- ((ChangeListening.Subject)g).addPropertyChangeListener(new PropertyChangeListener(){\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- repaint();\r
- area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
- volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
- }\r
- });\r
- }\r
-\r
-\r
- try {\r
-\r
- area = new Chart<Length, Area>(\r
- SI.MILLIMETER,\r
- SI.MILLIMETER.pow(2).asType(Area.class),\r
- grain,\r
- "surfaceArea");\r
- area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
- \r
- volume = new Chart<Length, Volume>(\r
- SI.MILLIMETER,\r
- SI.MILLIMETER.pow(3).asType(Volume.class),\r
- grain,\r
- "volume");\r
- volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-\r
- area.setMaximumSize(new Dimension(200,100));\r
- volume.setMaximumSize(new Dimension(200,100));\r
- \r
-\r
- } catch (ClassCastException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- } catch (NoSuchMethodException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- }\r
- \r
- addComponents(\r
- xc = new XC(grain),\r
- new SL(),\r
- l,\r
- area,\r
- volume\r
- );\r
- }\r
- \r
- protected void addComponents(\r
- Component crossSection,\r
- Component slider,\r
- Component label,\r
- Component area,\r
- Component volume\r
- ){\r
- \r
- JSplitPane v = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r
- JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\r
- \r
- JPanel graphics = new JPanel(new BorderLayout());\r
- graphics.add(crossSection, BorderLayout.CENTER);\r
- graphics.add(label, BorderLayout.NORTH);\r
- graphics.add(slider, BorderLayout.SOUTH);\r
- \r
- v.setTopComponent(h);\r
- v.setBottomComponent(area);\r
- h.setLeftComponent(graphics);\r
- h.setRightComponent(volume);\r
- add(v);\r
- \r
- h.resetToPreferredSizes();\r
- v.resetToPreferredSizes();\r
-\r
- }\r
- \r
- public void setDisplayedRegression( Amount<Length> r ){\r
- displayedRegression = r;\r
- \r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
- \r
- area.mark(displayedRegression);\r
- volume.mark(displayedRegression);\r
- if ( xc != null )\r
- xc.repaint();\r
- }\r
- \r
- private class XC extends JPanel{\r
- private static final long serialVersionUID = 1L;\r
- Grain grain;\r
- public XC(Grain g){\r
- grain = g;\r
- java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
- \r
- Rectangle bounds = unburnt.getBounds();\r
- double max = bounds.getWidth();\r
- if ( bounds.getHeight() > max )\r
- max = bounds.getHeight();\r
- int w = (int)(bounds.getWidth() * 200.0 / max);\r
- if ( w < 40 )\r
- w = 40;\r
- \r
- Dimension sz = new Dimension(240+w, 250);\r
- setMinimumSize(sz);\r
- setPreferredSize(sz);\r
- setMaximumSize(sz);\r
- }\r
- public void paint(Graphics g){\r
- super.paint(g);\r
- Graphics2D g2d = (Graphics2D)g;\r
- g2d.translate(10, 30);\r
- /*\r
- grain.draw(g2d, displayedRegression );\r
- */\r
- \r
- {\r
- AffineTransform t = g2d.getTransform();\r
- java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
- \r
- Rectangle bounds = unburnt.getBounds();\r
- g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
- g2d.translate(-bounds.getX(), -bounds.getY());\r
- \r
- //Draw the fuel that is left\r
- java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
- g2d.setColor(Color.RED);\r
- g2d.fill(burning);\r
- //Draw the fuel that is left\r
- java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
- g2d.setColor(Color.GRAY);\r
- g2d.fill(left);\r
- //Draw the outline of the unburnt grain\r
- g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));\r
- g2d.setColor(Color.BLACK);\r
- g2d.draw(unburnt);\r
- //untranslate\r
- g2d.setTransform(t);\r
- }\r
- {\r
- AffineTransform t = g2d.getTransform();\r
- java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
- \r
- Rectangle bounds = unburnt.getBounds();\r
- g2d.translate(220, 0);\r
- \r
- double max = bounds.getWidth();\r
- if ( bounds.getHeight() > max )\r
- max = bounds.getHeight();\r
- \r
- g2d.scale(200 / max, 200 / max);\r
- g2d.translate(-bounds.getX(), -bounds.getY());\r
- \r
- //Draw the fuel that is left\r
- java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
- g2d.setColor(Color.RED);\r
- g2d.fill(burning);\r
- //Draw the fuel that is left\r
- java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
- g2d.setColor(Color.GRAY);\r
- g2d.fill(left);\r
- //Draw the outline of the unburnt grain\r
- g2d.setColor(Color.BLACK);\r
- g2d.draw(unburnt);\r
- //untranslate\r
- g2d.setTransform(t);\r
- }\r
- \r
- }\r
- }\r
- \r
- private class SL extends JSlider implements ChangeListener{\r
- private static final long serialVersionUID = 1L;\r
- private static final int STEPS = 60;\r
- public SL(){\r
- addChangeListener(this);\r
- setMinimum(0);\r
- setMaximum(STEPS);\r
- setValue(0);\r
- }\r
- \r
- public void stateChanged(ChangeEvent e) {\r
- double r = ((SL)e.getSource()).getValue();\r
-\r
- setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
- }\r
- }\r
- \r
- public void showAsWindow(){\r
- JFrame f = new JFrame();\r
- f.setTitle(grain.getClass().getName());\r
- f.setSize(1024,600);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
-\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.Shape;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JPanel;\r
-import javax.swing.WindowConstants;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Chamber;\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.billkuker.rocketry.motorsim.Nozzle;\r
-\r
-public class HardwarePanel extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Nozzle nozzle;\r
- private Chamber chamber;\r
- \r
- public HardwarePanel(Nozzle n, Chamber c){\r
- nozzle = n;\r
- chamber = c;\r
- if ( n instanceof ChangeListening.Subject ){\r
- ((ChangeListening.Subject)n).addPropertyChangeListener(new PropertyChangeListener(){\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- repaint();\r
- }\r
- });\r
- }\r
- if ( c instanceof ChangeListening.Subject ){\r
- ((ChangeListening.Subject)c).addPropertyChangeListener(new PropertyChangeListener(){\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- repaint();\r
- }\r
- });\r
- }\r
- }\r
- \r
- public void paint(Graphics g){\r
- super.paint(g);\r
- Graphics2D g2d = (Graphics2D)g;\r
- g2d.translate(10, 10);\r
-\r
- g2d.setColor(Color.black);\r
- \r
- Shape c = chamber.chamberShape();\r
- \r
- Shape n = nozzle.nozzleShape(((CylindricalChamber)chamber).getID());\r
- \r
- Rectangle cb = c.getBounds();\r
- Rectangle nb = n.getBounds();\r
- double w, h;\r
- w = Math.max(cb.getWidth(), nb.getWidth());\r
- h = cb.getHeight() + nb.getHeight();\r
- \r
- double mw, mh;\r
- mw = getHeight() - 10;\r
- mh = getWidth() - 10;\r
- \r
- double sw, sh, s;\r
- sw = mw / w;\r
- sh = mh / h;\r
- s = Math.min(sw, sh);\r
- \r
- g2d.rotate(-Math.PI / 2);\r
- \r
- g2d.translate(0, -cb.getY() - 5);\r
- g2d.scale(s, s);\r
- g2d.translate(-(getHeight()/(s*2)), 0);\r
- \r
- g2d.setStroke(new BasicStroke(1));\r
- g2d.draw( c );\r
- g2d.translate(0, cb.getHeight());\r
- \r
- g2d.draw(n);\r
- }\r
- \r
- public void showAsWindow(){\r
- JFrame f = new JFrame();\r
- f.setSize(220,250);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
- \r
- public static void main(String args[]) throws Exception{\r
- ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
- CylindricalChamber c = new CylindricalChamber();\r
- n.setThroatDiameter(Amount.valueOf(10, SI.MILLIMETER));\r
- n.setExitDiameter(Amount.valueOf(20, SI.MILLIMETER));\r
- //new Editor(n).showAsWindow();\r
- new HardwarePanel(n,c).showAsWindow();\r
- }\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import com.billkuker.rocketry.motorsim.Burn;
-
-public interface BurnWatcher {
- public void replace( Burn oldBurn, Burn newBurn);
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.FocusEvent;\r
-import java.awt.event.FocusListener;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.beans.PropertyVetoException;\r
-import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import java.util.Vector;\r
-\r
-import javax.measure.quantity.Length;\r
-import javax.measure.unit.SI;\r
-import javax.swing.Box;\r
-import javax.swing.BoxLayout;\r
-import javax.swing.ComboBoxModel;\r
-import javax.swing.JButton;\r
-import javax.swing.JComboBox;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JProgressBar;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JTabbedPane;\r
-import javax.swing.JTextArea;\r
-import javax.swing.JTextField;\r
-import javax.swing.SwingUtilities;\r
-import javax.swing.UIManager;\r
-import javax.swing.WindowConstants;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\r
-import org.fife.ui.rsyntaxtextarea.SyntaxConstants;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Chamber;\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.billkuker.rocketry.motorsim.Fuel;\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.Motor;\r
-import com.billkuker.rocketry.motorsim.Nozzle;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.grain.CSlot;\r
-import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
-import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
-import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
-import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
-import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
-import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
-import com.billkuker.rocketry.motorsim.io.MotorIO;\r
-import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
-import com.billkuker.rocketry.motorsim.visual.Editor;\r
-import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
-import com.billkuker.rocketry.motorsim.visual.HardwarePanel;\r
-\r
-public class MotorEditor extends JTabbedPane implements PropertyChangeListener {\r
- private static final long serialVersionUID = 1L;\r
- private static Logger log = Logger.getLogger(MotorEditor.class);\r
- RSyntaxTextArea text = new RSyntaxTextArea();\r
- Motor motor;\r
- GrainEditor grainEditor;\r
- BurnTab bt;\r
- Burn burn;\r
-\r
- private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
- private ComboBoxModel availableFuels;\r
-\r
- //private static final int XML_TAB = 0;\r
- private static final int CASING_TAB = 0;\r
- private static final int GRAIN_TAB = 1;\r
- private static final int BURN_TAB = 2;\r
-\r
- @SuppressWarnings("unchecked")\r
- private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,\r
- Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };\r
-\r
- private abstract class Chooser<T> extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Class<? extends T>[] types;\r
- private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
-\r
- public Chooser(T initial, Class<? extends T>... ts) {\r
- types = ts;\r
- if ( initial != null )\r
- old.put((Class<? extends T>)initial.getClass(), initial);\r
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
- for (final Class<? extends T> c : types) {\r
- JButton b = new JButton(c.getSimpleName());\r
- add(b);\r
- b.addActionListener(new ActionListener() {\r
- public void actionPerformed(ActionEvent e) {\r
- try {\r
- T val = old.get(c);\r
- if ( val == null ){\r
- System.err.println("CREATED NEW =========================");\r
- val = c.newInstance();\r
- old.put(c, val);\r
- }\r
- choiceMade(val);\r
- } catch (InstantiationException e1) {\r
- e1.printStackTrace();\r
- } catch (IllegalAccessException e1) {\r
- e1.printStackTrace();\r
- }\r
- }\r
- });\r
- }\r
- }\r
-\r
- protected abstract void choiceMade(T o);\r
- }\r
-\r
- private class BurnTab extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Thread currentThread;\r
- \r
- public BurnTab() {\r
- setLayout(new BorderLayout());\r
- setName("Simulation Results");\r
- reBurn();\r
- }\r
- \r
- private class BurnCanceled extends RuntimeException{\r
- private static final long serialVersionUID = 1L;\r
- };\r
-\r
- public void reBurn() {\r
- removeAll();\r
- currentThread = new Thread() {\r
- public void run() {\r
- final Thread me = this;\r
- final JProgressBar bar = new JProgressBar(0, 100);\r
- add(bar, BorderLayout.NORTH);\r
- final JLabel progress = new JLabel();\r
- add(progress, BorderLayout.CENTER);\r
- try {\r
- final Burn b = new Burn(motor,\r
- new Burn.BurnProgressListener() {\r
- @Override\r
- public void setProgress(float f) {\r
- int pct = (int)(f*100);\r
- bar.setValue(pct);\r
- Amount<Length> web = motor.getGrain().webThickness();\r
- Amount<Length> remaining = web.times(1.0 - f);\r
- \r
- progress.setText("Progress: " + pct + "% (" + RocketScience.approx(remaining) + " web thickness remaining)");\r
- if ( currentThread != me ){\r
- throw new BurnCanceled();\r
- }\r
- }\r
- });\r
-\r
- final BurnPanel bp = new BurnPanel(b);\r
- SwingUtilities.invokeLater(new Thread() {\r
- public void run() {\r
- remove(bar);\r
- add(bp, BorderLayout.CENTER);\r
-\r
- for (BurnWatcher bw : burnWatchers)\r
- bw.replace(burn, b);\r
- burn = b;\r
-\r
- revalidate();\r
- }\r
- });\r
- } catch (BurnCanceled c){\r
- log.info("Burn Canceled!");\r
- } catch (Exception e) {\r
- remove(bar);\r
- JTextArea t = new JTextArea(e.getMessage());\r
- t.setEditable(false);\r
- add(t);\r
- }\r
- }\r
- };\r
- currentThread.start();\r
- }\r
- }\r
-\r
- private class GrainEditor extends JSplitPane {\r
- private static final long serialVersionUID = 1L;\r
-\r
- public GrainEditor(final Grain g) {\r
- super(JSplitPane.HORIZONTAL_SPLIT);\r
- setName("Grain Geometry");\r
- setRightComponent(new GrainPanel(g));\r
- if (g instanceof Grain.Composite) {\r
- final JPanel p = new JPanel();\r
- p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
- p.add(new Editor(g));\r
- for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
- final int grainEditorIndex = p.getComponentCount() + 1;\r
- p.add(new Chooser<Grain>(gg, grainTypes) {\r
- private static final long serialVersionUID = 1L;\r
-\r
- @Override\r
- protected void choiceMade(Grain ng) {\r
- if (g instanceof MultiGrain) {\r
- ((MultiGrain) g).setGrain(ng);\r
- p.remove(grainEditorIndex);\r
- p.add(new Editor(ng), grainEditorIndex);\r
- p.remove(0);\r
- p.add(new Editor(g), 0);\r
- }\r
- }\r
- });\r
- p.add(new Editor(gg));\r
- if (gg instanceof ChangeListening.Subject) {\r
- ((ChangeListening.Subject) gg)\r
- .addPropertyChangeListener(MotorEditor.this);\r
- }\r
- }\r
- setLeftComponent(p);\r
- } else {\r
- setLeftComponent(new Editor(g));\r
- }\r
- // setDividerLocation(.25);\r
- // setResizeWeight(.25);\r
- if (g instanceof ChangeListening.Subject) {\r
- ((ChangeListening.Subject) g)\r
- .addPropertyChangeListener(MotorEditor.this);\r
- }\r
- }\r
- }\r
-\r
- private class CaseEditor extends JSplitPane {\r
- private static final long serialVersionUID = 1L;\r
-\r
- public CaseEditor(Nozzle n, Chamber c) {\r
- super(JSplitPane.VERTICAL_SPLIT);\r
- setName("General Parameters");\r
- \r
- JPanel parts = new JPanel();\r
- parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));\r
- setTopComponent(parts);\r
- setBottomComponent(new HardwarePanel(n, c));\r
- \r
- JPanel nameAndFuel = new JPanel();\r
- nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));\r
-\r
- nameAndFuel.add(new JLabel("Name:"));\r
- nameAndFuel.add(new JTextField(motor.getName()) {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- setMinimumSize(new Dimension(200, 20));\r
- setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
- final JTextField t = this;\r
- addFocusListener(new FocusListener() {\r
-\r
- @Override\r
- public void focusLost(FocusEvent e) {\r
- String n = t.getText();\r
- if (!"".equals(n) && !n.equals(motor.getName())) {\r
- motor.setName(n);\r
- } else {\r
- t.setText(motor.getName());\r
- }\r
- }\r
-\r
- @Override\r
- public void focusGained(FocusEvent e) {\r
-\r
- }\r
- });\r
-\r
- }\r
- });\r
- nameAndFuel.add(new JLabel("Fuel:"));\r
- nameAndFuel.add( new JComboBox(availableFuels){{\r
- setMinimumSize(new Dimension(200, 20));\r
- setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
- addActionListener(new ActionListener(){\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- motor.setFuel((Fuel)getSelectedItem());\r
- System.out.println("FUEL CHANGED");\r
- }});\r
- }});\r
- nameAndFuel.add(Box.createVerticalGlue());\r
- parts.add(nameAndFuel);\r
- \r
- JPanel casing = new JPanel();\r
- casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));\r
- casing.add(new JLabel("Casing:"));\r
- casing.add(new Editor(c));\r
- parts.add(casing);\r
- \r
- JPanel nozzle = new JPanel();\r
- nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));\r
- nozzle.add(new JLabel("Nozzle:"));\r
- nozzle.add(new Editor(n));\r
- parts.add(nozzle);\r
-\r
- if (n instanceof ChangeListening.Subject) {\r
- ((ChangeListening.Subject) n)\r
- .addPropertyChangeListener(MotorEditor.this);\r
- }\r
- if (c instanceof ChangeListening.Subject) {\r
- ((ChangeListening.Subject) c)\r
- .addPropertyChangeListener(MotorEditor.this);\r
- }\r
- }\r
- }\r
-\r
- {\r
- text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);\r
-\r
- }\r
-\r
- public MotorEditor(Motor m, ComboBoxModel fuels) {\r
- super(JTabbedPane.BOTTOM);\r
- this.availableFuels = fuels;\r
- text.setName("XML");\r
- text.setEditable(false);\r
- //add(text, XML_TAB);\r
- setMotor(m, true);\r
- }\r
-\r
- public Motor getMotor() {\r
- return motor;\r
- }\r
-\r
- private void reText() {\r
- try {\r
- text.setText(MotorIO.writeMotor(motor));\r
- } catch (IOException e) {\r
- throw new Error(e);\r
- }\r
- }\r
-\r
- private void setMotor(Motor m, boolean retext) {\r
- if (motor != null)\r
- motor.removePropertyChangeListener(this);\r
- motor = m;\r
- motor.addPropertyChangeListener(this);\r
- if (retext)\r
- reText();\r
- if (grainEditor != null)\r
- remove(grainEditor);\r
- while (getTabCount() > 1)\r
- removeTabAt(1);\r
- add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);\r
- add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
- add(bt = new BurnTab(), BURN_TAB);\r
- }\r
-\r
- @Deprecated\r
- public static Motor defaultMotor() {\r
- Motor m = new Motor();\r
- m.setName("Example Motor");\r
- m.setFuel(new KNSU());\r
-\r
- CylindricalChamber c = new CylindricalChamber();\r
- c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
- c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
- m.setChamber(c);\r
-\r
- CoredCylindricalGrain g = new CoredCylindricalGrain();\r
- try {\r
- g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
- g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
- g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
- } catch (PropertyVetoException v) {\r
- throw new Error(v);\r
- }\r
-\r
- m.setGrain(new MultiGrain(g, 2));\r
-\r
- ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
- n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
- n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
- n.setEfficiency(.85);\r
- m.setNozzle(n);\r
-\r
- return m;\r
- }\r
-\r
- public void focusOnObject(Object o) {\r
- if (o instanceof Grain)\r
- setSelectedIndex(GRAIN_TAB);\r
- if (o instanceof Chamber || o instanceof Nozzle)\r
- setSelectedIndex(CASING_TAB);\r
- }\r
-\r
- public void addBurnWatcher(BurnWatcher bw) {\r
- burnWatchers.add(bw);\r
- }\r
-\r
- @Deprecated\r
- public void showAsWindow() {\r
- JFrame f = new JFrame();\r
- f.setSize(1024, 768);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
-\r
- public static void main(String args[]) throws Exception {\r
- try {\r
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
- } catch (Exception e1) {\r
- e1.printStackTrace();\r
- }\r
- Vector<Fuel> ff = new Vector<Fuel>();\r
- ff.add(new KNSU());\r
- //new MotorEditor(defaultMotor(), ff).showAsWindow();\r
- }\r
-\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- reText();\r
- // Dont re-burn for a name change!\r
- if (!evt.getPropertyName().equals("Name")){\r
- bt.reBurn();\r
- } else {\r
- for (BurnWatcher bw : burnWatchers)\r
- bw.replace(burn, burn);\r
- }\r
- }\r
-\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.FileDialog;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.io.File;\r
-import java.util.HashMap;\r
-import java.util.Vector;\r
-\r
-import javax.swing.ButtonGroup;\r
-import javax.swing.DefaultComboBoxModel;\r
-import javax.swing.JFrame;\r
-import javax.swing.JMenu;\r
-import javax.swing.JMenuBar;\r
-import javax.swing.JMenuItem;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JRadioButtonMenuItem;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JSeparator;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JTabbedPane;\r
-import javax.swing.JTree;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.TreeSelectionEvent;\r
-import javax.swing.event.TreeSelectionListener;\r
-import javax.swing.tree.DefaultMutableTreeNode;\r
-import javax.swing.tree.TreePath;\r
-import javax.swing.tree.TreeSelectionModel;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Fuel;\r
-import com.billkuker.rocketry.motorsim.Motor;\r
-import com.billkuker.rocketry.motorsim.RocketScience.UnitPreference;\r
-import com.billkuker.rocketry.motorsim.fuel.KNDX;\r
-import com.billkuker.rocketry.motorsim.fuel.KNER;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSB;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.io.ENGExporter;\r
-import com.billkuker.rocketry.motorsim.io.MotorIO;\r
-import com.billkuker.rocketry.motorsim.visual.FuelPanel;\r
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelEditNode;\r
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;\r
-\r
-public class MotorWorkbench extends JFrame implements TreeSelectionListener {\r
- private static final long serialVersionUID = 1L;\r
- \r
- @SuppressWarnings("unchecked")\r
- private Class[] fuelTypes = { KNSB.class, KNSU.class, KNER.class,\r
- KNDX.class };\r
- \r
- private JPanel top;\r
- private JSplitPane split;\r
- private JTree tree;\r
- private JTabbedPane motors;\r
- private WorkbenchTreeModel tm;\r
- private MultiBurnChart mb;\r
- private JFrame allBurns;\r
-\r
- private HashMap<MotorEditor, File> e2f = new HashMap<MotorEditor, File>();\r
- private HashMap<File, MotorEditor> f2e = new HashMap<File, MotorEditor>();\r
-\r
- private HashMap<Motor, MotorEditor> m2e = new HashMap<Motor, MotorEditor>();\r
- \r
- private DefaultComboBoxModel fuels = new DefaultComboBoxModel();\r
- \r
- public MotorWorkbench() {\r
- setTitle("MotorSim 1.0 RC1");\r
- addMenu();\r
- setSize(1024, 768);\r
- top = new JPanel(new BorderLayout());\r
- setContentPane(top);\r
- \r
- mb = new MultiBurnChart();\r
- allBurns = new JFrame();\r
- allBurns.setTitle("All Burns");\r
- allBurns.setSize(800, 600);\r
- setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);\r
- allBurns.add(mb);\r
-\r
- motors = new JTabbedPane();\r
-\r
- tree = new JTree(tm = new WorkbenchTreeModel());\r
- tree.setCellRenderer(new WorkbenchTreeCellRenderer());\r
- tree.getSelectionModel().setSelectionMode(\r
- TreeSelectionModel.SINGLE_TREE_SELECTION);\r
- tree.setMinimumSize(new Dimension(200, 100));\r
-\r
- // Listen for when the selection changes.\r
- tree.addTreeSelectionListener(this);\r
-\r
- split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(\r
- tree), motors);\r
- split.setDividerLocation(200);\r
- split.setResizeWeight(0);\r
- split.resetToPreferredSizes();\r
- split.revalidate();\r
- \r
- top.add(split, BorderLayout.CENTER);\r
- \r
- for ( Class<Fuel> f : fuelTypes){\r
- try {\r
- addFuel(f.newInstance());\r
- } catch (InstantiationException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- } catch (IllegalAccessException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- }\r
- }\r
-\r
- setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);\r
- setVisible(true);\r
-\r
- }\r
-\r
- private void addMenu() {\r
-\r
- setJMenuBar(new JMenuBar() {\r
- private static final long serialVersionUID = 1L;\r
-\r
- {\r
- add(new JMenu("File") {\r
- private static final long serialVersionUID = 1L;\r
-\r
- {\r
- add(new JMenuItem("New Motor") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
-\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
- addMotor(MotorEditor.defaultMotor(),\r
- null);\r
- }\r
- });\r
-\r
- }\r
- });\r
- add(new JMenuItem("Open...") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
-\r
- final FileDialog fd = new FileDialog(MotorWorkbench.this, "Open Motor", FileDialog.LOAD);\r
- fd.setVisible(true);\r
- if ( fd.getFile() != null ) {\r
- File file = new File(fd.getDirectory() + fd.getFile());\r
- if (f2e.get(file) != null) {\r
- motors.setSelectedComponent(f2e\r
- .get(file));\r
- return;\r
- }\r
- try {\r
- Motor m = MotorIO\r
- .readMotor(file);\r
- addMotor(m, file);\r
- } catch (Exception e) {\r
- JOptionPane.showMessageDialog(\r
- MotorWorkbench.this, e\r
- .getMessage());\r
- }\r
- }\r
- }\r
- });\r
- }\r
- });\r
- \r
- add(new JSeparator());\r
- \r
- add(new JMenuItem("Close Motor") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent ev) {\r
- MotorEditor e = (MotorEditor) motors\r
- .getSelectedComponent();\r
- tm.removeMotor(e.getMotor());\r
- motors.remove(e);\r
- f2e.remove(e2f.get(e));\r
- e2f.remove(e);\r
- m2e.remove(e.getMotor());\r
- mb.removeBurn(e.burn);\r
- }\r
- });\r
- }\r
- });\r
- \r
- add(new JMenuItem("Save Motor") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- MotorEditor m = (MotorEditor) motors\r
- .getSelectedComponent();\r
- File f = e2f.get(m);\r
- if (f != null)\r
- save(m.getMotor(), f);\r
-\r
- }\r
- });\r
- }\r
- });\r
- add(new JMenuItem("Save Motor As...") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
-\r
- final FileDialog fd = new FileDialog(MotorWorkbench.this, "Save Motor As", FileDialog.SAVE);\r
- fd.setVisible(true);\r
- if (fd.getFile() != null ) {\r
- File file = new File(fd.getDirectory() + fd.getFile());\r
- MotorEditor m = (MotorEditor) motors\r
- .getSelectedComponent();\r
- try {\r
- save(m.getMotor(), file);\r
- e2f.put(m, file);\r
- f2e.put(file, m);\r
- motors.setTitleAt(motors\r
- .getSelectedIndex(),\r
- file.getName());\r
- // TODO Set tab title\r
- } catch (Exception e) {\r
- JOptionPane.showMessageDialog(\r
- MotorWorkbench.this, e\r
- .getMessage());\r
- }\r
- }\r
- }\r
- });\r
- }\r
- });\r
- \r
-\r
- add(new JSeparator());\r
- add(new JMenuItem("New Fuel") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
- newFuel();\r
- }\r
- });\r
-\r
- }\r
- });\r
- add(new JMenuItem("Save Fuel") {});\r
- add(new JSeparator());\r
- add(new JMenuItem("Export .ENG"){\r
- private static final long serialVersionUID = 1L;\r
-\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
-\r
- final FileDialog fd = new FileDialog(MotorWorkbench.this, "Export .ENG File", FileDialog.SAVE);\r
- fd.setFile("motorsim.eng");\r
- fd.setVisible(true);\r
- if (fd.getFile() != null ) {\r
- File file = new File(fd.getDirectory() + fd.getFile());\r
- Vector<Burn> bb = new Vector<Burn>();\r
- for( MotorEditor me : m2e.values() )\r
- bb.add(me.burn);\r
- try{\r
- ENGExporter.export(bb, file);\r
- } catch ( Exception e ){\r
- e.printStackTrace();\r
- }\r
- }\r
- }\r
- });\r
- }\r
- });\r
- }\r
- });\r
- add(new JMenu("Settings") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- ButtonGroup units = new ButtonGroup();\r
- JRadioButtonMenuItem sci = new JRadioButtonMenuItem(\r
- "SI");\r
- JRadioButtonMenuItem nonsci = new JRadioButtonMenuItem(\r
- "NonSI");\r
- units.add(sci);\r
- units.add(nonsci);\r
- sci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.SI);\r
- nonsci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.NONSI);\r
- sci.addActionListener(new ActionListener() {\r
- public void actionPerformed(ActionEvent arg0) {\r
- UnitPreference\r
- .setUnitPreference(UnitPreference.SI);\r
- }\r
- });\r
- nonsci.addActionListener(new ActionListener() {\r
- public void actionPerformed(ActionEvent arg0) {\r
- UnitPreference\r
- .setUnitPreference(UnitPreference.NONSI);\r
- }\r
- });\r
- add(sci);\r
- add(nonsci);\r
- }\r
- });\r
- add(new JMenu("View") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- add(new JMenuItem("Show All Motors Graph") {\r
- private static final long serialVersionUID = 1L;\r
- {\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent arg0) {\r
- allBurns.setVisible(true);\r
- allBurns.toFront();\r
- }\r
- });\r
- }\r
- });\r
- }\r
- });\r
- }\r
- });\r
- }\r
- \r
- private void addFuel(Fuel f){\r
- fuels.addElement(f);\r
- FuelPanel fp = new FuelPanel(f);\r
- FuelNode fn = tm.new FuelNode(fp, f);\r
- tm.getFuels().add(fn);\r
- tm.nodeStructureChanged(tm.getFuels());\r
- motors.addTab(f.getName(), fp);\r
- }\r
- \r
- private void newFuel(){\r
- final SRFuelEditor ed = new SRFuelEditor();\r
- fuels.addElement(ed.getFuel());\r
- final FuelEditNode node = tm.new FuelEditNode(ed);\r
- tm.getFuels().add(node);\r
- tm.nodeStructureChanged(tm.getFuels());\r
- motors.addTab(ed.getFuel().getName(), ed);\r
- ed.getFuel().addPropertyChangeListener(new PropertyChangeListener(){\r
- @Override\r
- public void propertyChange(PropertyChangeEvent evt) {\r
- if ( evt.getPropertyName().equals("Name")){\r
- for ( int i = 0; i < motors.getTabCount(); i++ ){\r
- if ( motors.getComponent(i) == ed ){\r
- motors.setTitleAt(i, ed.getFuel().getName());\r
- tm.nodeChanged(node);\r
- }\r
- }\r
- }\r
- }});\r
- }\r
-\r
- private void save(Motor m, File f) {\r
- try {\r
- MotorIO.writeMotor(m, f);\r
- } catch (Throwable t) {\r
- JOptionPane.showMessageDialog(MotorWorkbench.this, t.getMessage());\r
- }\r
- }\r
-\r
- public void addMotor(Motor m, File f) {\r
- tm.addMotor(m);\r
- MotorEditor e = new MotorEditor(m, fuels);\r
- e.addBurnWatcher(mb);\r
- String title;\r
- if (f == null) {\r
- title = "New Motor";\r
- } else {\r
- title = f.getName();\r
- e2f.put(e, f);\r
- f2e.put(f, e);\r
- }\r
- m2e.put(m, e);\r
- motors.addTab(title, e);\r
- }\r
-\r
- @Override\r
- public void valueChanged(TreeSelectionEvent e) { \r
- if ( e.getPath().getLastPathComponent() instanceof FuelNode ){\r
- FuelNode fen = ((FuelNode)e.getPath().getLastPathComponent());\r
- motors.setSelectedComponent(fen.getUserObject()); \r
- }\r
- \r
- Motor m = getMotor(e.getPath());\r
- \r
- if ( m == null )\r
- return;\r
-\r
- motors.setSelectedComponent(m2e.get(m));\r
-\r
- if (e.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) {\r
- Object o = ((DefaultMutableTreeNode) e.getPath()\r
- .getLastPathComponent()).getUserObject();\r
- m2e.get(m).focusOnObject(o);\r
- }\r
- \r
-\r
- }\r
-\r
- private Motor getMotor(TreePath p) {\r
- if (p.getLastPathComponent() instanceof WorkbenchTreeModel.MotorNode) {\r
- return ((WorkbenchTreeModel.MotorNode) p.getLastPathComponent())\r
- .getUserObject();\r
- } else if (p.getPath().length > 1)\r
- return getMotor(p.getParentPath());\r
- return null;\r
- }\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.BorderLayout;
-import java.util.HashMap;
-
-import javax.measure.quantity.Duration;
-import javax.measure.quantity.Force;
-import javax.measure.unit.SI;
-import javax.measure.unit.Unit;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.WindowConstants;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.data.xy.XYSeries;
-import org.jfree.data.xy.XYSeriesCollection;
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Burn;
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.RocketScience;
-
-public class MultiBurnChart extends JPanel implements BurnWatcher {
- private static final long serialVersionUID = 1L;
-
- private XYSeriesCollection dataset = new XYSeriesCollection();
-
- private HashMap<Burn, XYSeries> burnToSeries = new HashMap<Burn, XYSeries>();
- private Unit<Duration> time;
- private Unit<Force> force;
-
- @SuppressWarnings("unchecked")
- public MultiBurnChart() {
- this.setLayout(new BorderLayout());
- time = RocketScience.UnitPreference.getUnitPreference()
- .getPreferredUnit(SI.SECOND);
- force = RocketScience.UnitPreference.getUnitPreference()
- .getPreferredUnit(SI.NEWTON);
- JFreeChart chart = ChartFactory.createXYLineChart(
- "", // Title
- time.toString(), // x-axis Label
- force.toString(), // y-axis Label
- dataset, PlotOrientation.VERTICAL, // Plot Orientation
- true, // Show Legend
- true, // Use tool tips
- false // Configure chart to generate URLs?
- );
- add(new ChartPanel(chart));
- }
-
- public void addBurn(Burn b) {
- XYSeries s = createSeries(b);
- burnToSeries.put(b, s);
- dataset.addSeries(s);
- }
-
- private XYSeries createSeries(Burn b) {
- XYSeries s = new XYSeries(b.getMotor().getName());
- for( Burn.Interval i : b.getData().values() ){
- s.add(i.time.doubleValue(time), i.thrust.doubleValue(force));
- }
- return s;
- }
-
- public void removeBurn(Burn b) {
- XYSeries s = burnToSeries.get(b);
- if (s == null)
- return;
- dataset.removeSeries(s);
- }
-
- public static void main(String args[]) throws Exception{
- MultiBurnChart c = new MultiBurnChart();
-
- JFrame f = new JFrame();
- f.setSize(1024, 768);
- f.setContentPane(c);
- f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- f.setVisible(true);
-
- Motor m = MotorEditor.defaultMotor();
- Burn b = new Burn(m);
- c.addBurn(b);
-
- m.setName("Motor2");
- ((ConvergentDivergentNozzle)m.getNozzle()).setThroatDiameter(Amount.valueOf(3, SI.MILLIMETER));
- c.addBurn(new Burn(m));
-
- Thread.sleep(5000);
-
- c.removeBurn(b);
-
- }
-
- @Override
- public void replace(Burn oldBurn, Burn newBurn) {
- removeBurn(oldBurn);
- addBurn(newBurn);
- }
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.beans.PropertyChangeEvent;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Collections;
-import java.util.Vector;
-
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Velocity;
-import javax.measure.quantity.VolumetricDensity;
-import javax.measure.unit.SI;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
-import javax.swing.JSplitPane;
-import javax.swing.JTable;
-import javax.swing.SwingUtilities;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.table.AbstractTableModel;
-
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Fuel;
-import com.billkuker.rocketry.motorsim.RocketScience;
-import com.billkuker.rocketry.motorsim.fuel.EditableFuel.EditableCombustionProduct;
-import com.billkuker.rocketry.motorsim.fuel.PiecewiseSaintRobertFuel;
-import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel;
-import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel.Type;
-import com.billkuker.rocketry.motorsim.visual.Chart;
-import com.billkuker.rocketry.motorsim.visual.Editor;
-
-public class SRFuelEditor extends JSplitPane {
- private static final NumberFormat nf = new DecimalFormat("##########.###");
-
- Chart<Pressure, Velocity> burnRate;
-
- private class Entry implements Comparable<Entry> {
- Amount<Pressure> p = Amount.valueOf(0, RocketScience.UnitPreference.getUnitPreference().getPreferredUnit(RocketScience.PSI));
- double a;
- double n;
-
- @Override
- public int compareTo(Entry o) {
- return p.compareTo(o.p);
- }
- }
-
- public static class EditablePSRFuel extends PiecewiseSaintRobertFuel {
-
- private Amount<VolumetricDensity> idealDensity = (Amount<VolumetricDensity>) Amount
- .valueOf("1 g/mm^3");
- private double combustionEfficiency = 1;
- private double densityRatio = 1;
- private EditableCombustionProduct cp;
- private String name = "New Fuel";
-
- public EditablePSRFuel(Type t) {
- super(t);
- cp = new EditableCombustionProduct();
- }
-
- public void clear(){
- super.clear();
- }
-
- public void setType(Type t){
- super.setType(t);
- }
-
- public void add(Amount<Pressure> p, final double _a, final double _n) {
- super.add(p, _a, _n);
-
- }
-
- public Amount<VolumetricDensity> getIdealDensity() {
- return idealDensity;
- }
-
- public void setIdealDensity(Amount<VolumetricDensity> idealDensity) {
- this.idealDensity = idealDensity;
- }
-
- public double getCombustionEfficiency() {
- return combustionEfficiency;
- }
-
- public void setCombustionEfficiency(double combustionEfficiency) {
- this.combustionEfficiency = combustionEfficiency;
- }
-
- public double getDensityRatio() {
- return densityRatio;
- }
-
- public void setDensityRatio(double densityRatio) {
- this.densityRatio = densityRatio;
- }
-
- @Override
- public CombustionProduct getCombustionProduct() {
- return cp;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- }
-
- final EditablePSRFuel f = new EditablePSRFuel(SaintRobertFuel.Type.SI);
-
- private class TM extends AbstractTableModel {
-
- @Override
- public int getColumnCount() {
- return 3;
- }
-
- @Override
- public int getRowCount() {
- return entries.size();
- }
-
- @Override
- public String getColumnName(int col) {
- switch (col) {
- case 0:
- return "Pressure";
- case 1:
- return "Coefficient (a)";
- case 2:
- return "Exponent (n)";
- }
- return null;
- }
-
- @Override
- public Object getValueAt(int rowIndex, int columnIndex) {
- Entry e = entries.get(rowIndex);
- switch (columnIndex) {
- case 0:
- //Format like 100 psi or 4.8 Mpa
- return nf.format(e.p.doubleValue(e.p.getUnit())) + " " + e.p.getUnit();
- case 1:
- return e.a;
- case 2:
- return e.n;
- }
- return null;
- }
-
- public boolean isCellEditable(int row, int col) {
- return true;
- }
-
- public void setValueAt(Object value, int row, int col) {
- Entry e = entries.get(row);
- try {
- switch (col) {
- case 0:
- try {
- e.p = (Amount<Pressure>) Amount.valueOf((String) value);
- } catch ( Exception ee ){
- double d = Double.parseDouble((String)value);
- e.p = (Amount<Pressure>)Amount.valueOf(d, e.p.getUnit());
- }
- break;
- case 1:
- e.a = Double.valueOf((String) value);
- break;
- case 2:
- e.n = Double.valueOf((String) value);
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- Collections.sort(entries);
- fireTableDataChanged();
- //f = new EditablePSRFuel(SaintRobertFuel.Type.NONSI);
- f.clear();
- for (Entry en : entries) {
- f.add(en.p, en.a, en.n);
- }
- f.firePropertyChange(new PropertyChangeEvent(f,"entries", null, null));
-
- update();
-
- }
-
- @Override
- public void fireTableDataChanged() {
- super.fireTableDataChanged();
- }
-
- };
-
- public Fuel getFuel(){
- return f;
- }
-
- private void update() {
- SwingUtilities.invokeLater(new Runnable() {
-
- @Override
- public void run() {
- editTop.setTopComponent(new Editor(f));
- editTop.setBottomComponent(new Editor(f.getCombustionProduct()));
- if (burnRate != null)
- SRFuelEditor.this.remove(burnRate);
- try {
- burnRate = new Chart<Pressure, Velocity>(
- SI.MEGA(SI.PASCAL), SI.MILLIMETER.divide(SI.SECOND)
- .asType(Velocity.class), f, "burnRate");
- } catch (NoSuchMethodException e) {
- throw new Error(e);
- }
- burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(
- 0, SI.MEGA(SI.PASCAL)), Amount.valueOf(11, SI
- .MEGA(SI.PASCAL)), 50));
- SRFuelEditor.this.setRightComponent(burnRate);
- SRFuelEditor.this.revalidate();
- }
- });
- }
-
- private Vector<Entry> entries = new Vector<Entry>();
-
- JSplitPane editParent;
- JSplitPane editTop;
- JSplitPane editBottom;
- JPanel controls;
-
- public SRFuelEditor() {
- super(HORIZONTAL_SPLIT);
- setResizeWeight(0);
- setDividerLocation(.3);
-
- final TM tm = new TM();
-
- editParent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
- editTop = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
- editBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
-
- editParent.setTopComponent(editTop);
- editParent.setBottomComponent(editBottom);
-
- editTop.setTopComponent(new Editor(f));
-
- JTable table = new JTable(tm);
- JScrollPane scrollpane = new JScrollPane(table);
- scrollpane.setMinimumSize(new Dimension(200, 200));
- editBottom.setTopComponent(scrollpane);
-
- setLeftComponent(editParent);
-
- JButton add = new JButton("Add Data");
- add.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- entries.add(new Entry());
- tm.fireTableDataChanged();
- }
- });
- controls = new JPanel();
- controls.setPreferredSize(new Dimension(200, 50));
- controls.setLayout(new FlowLayout());
-
- controls.add(add);
-
-
- final JRadioButton si, nonsi;
- ButtonGroup type = new ButtonGroup();
- JPanel radio = new JPanel();
- radio.add(si = new JRadioButton("SI"));
- radio.add(nonsi = new JRadioButton("NonSI"));
- controls.add(radio);
- type.add(si);
- type.add(nonsi);
-
- si.setSelected(true);
-
- si.addChangeListener(new ChangeListener(){
- @Override
- public void stateChanged(ChangeEvent e) {
- if ( si.isSelected() ){
- System.err.println("SI");
- f.setType(Type.SI);
- } else {
- System.err.println("NONSI");
- f.setType(Type.NONSI);
- }
- update();
- }});
-
- editBottom.setBottomComponent(controls);
-
-
- editParent.setDividerLocation(.5);
- editTop.setDividerLocation(.5);
- editBottom.setDividerLocation(.8);
-
- editParent.resetToPreferredSizes();
- revalidate();
-
- update();
- }
-
- public static void main(String args[]) {
- SRFuelEditor ed;
- JFrame f = new JFrame();
- f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- f.setContentPane(ed = new SRFuelEditor());
- f.setSize(800, 600);
- f.setVisible(true);
-
- }
-
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Color;
-import java.awt.Component;
-
-import javax.swing.JTree;
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeCellRenderer;
-
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.Validating;
-import com.billkuker.rocketry.motorsim.Validating.ValidationException;
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;
-
-public class WorkbenchTreeCellRenderer extends DefaultTreeCellRenderer {
- private static final long serialVersionUID = 1L;
-
- @Override
- public Component getTreeCellRendererComponent(JTree tree, final Object value,
- boolean sel, boolean expanded, boolean leaf, int row,
- boolean hasFocus) {
-
- String tip = null;
- setTextNonSelectionColor(Color.black);
- setTextSelectionColor(Color.white);
-
- Object part = null;
- if (value instanceof DefaultMutableTreeNode) {
- part = ((DefaultMutableTreeNode) value).getUserObject();
- }
-
- if ( part instanceof Validating ){
- try {
- ((Validating)part).validate();
- } catch (ValidationException e) {
- setTextSelectionColor(Color.RED);
- setTextNonSelectionColor(Color.RED);
- setToolTipText(e.getMessage());
- tip = e.getMessage();
- }
- }
-
- super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
- row, hasFocus);
-
- if (part instanceof Motor) {
- setText(((Motor) part).getName());
- } else if ( value instanceof FuelNode ){
- setText(((FuelNode)value).getFuel().getName());
- } else if ( part instanceof String ) {
- setText((String)part);
- } else if ( part == null ) {
- setText("");
- } else {
- setText(part.getClass().getSimpleName());
- }
- setToolTipText(tip);
-
-
-
- return this;
- }
-}
+++ /dev/null
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Component;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.Enumeration;
-
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeModel;
-import javax.swing.tree.TreeNode;
-
-import com.billkuker.rocketry.motorsim.ChangeListening;
-import com.billkuker.rocketry.motorsim.Fuel;
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.grain.MultiGrain;
-
-public class WorkbenchTreeModel extends DefaultTreeModel {
-
- private static final long serialVersionUID = 1L;
-
- //TreeNode root = new DefaultMutableTreeNode("Root");
- DefaultMutableTreeNode motors = new DefaultMutableTreeNode("All Motors");
- DefaultMutableTreeNode fuel = new DefaultMutableTreeNode("Fuels");
-
- public class MultiGrainNode extends PartNode{
- private static final long serialVersionUID = 1L;
- public MultiGrainNode(MultiGrain part) {
- super(part);
- setAllowsChildren(true);
- add(new PartNode(part.getGrain()));
- }
- @Override
- public void propertyChange(PropertyChangeEvent e) {
- if ( e.getPropertyName().equals("Grain")){
- remove(0);
- add(new PartNode(((MultiGrain)getUserObject()).getGrain()));
- nodesChanged(this, new int[]{0});
- }
- super.propertyChange(e);
- }
- }
-
- public class FuelNode extends DefaultMutableTreeNode{
- private static final long serialVersionUID = 1L;
- Fuel f;
- public FuelNode(Component c, Fuel f){
- super(c, false);
- this.f = f;
- }
-
- @Override
- public Component getUserObject(){
- return (Component)super.getUserObject();
- }
-
- public Fuel getFuel(){
- return f;
- }
- }
-
- public class FuelEditNode extends FuelNode {
- private static final long serialVersionUID = 1L;
-
- public FuelEditNode(SRFuelEditor sr){
- super(sr, sr.getFuel());
- sr.getFuel().addPropertyChangeListener(new PropertyChangeListener(){
-
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- nodeChanged(FuelEditNode.this);
- }});
- }
-
- @Override
- public SRFuelEditor getUserObject(){
- return (SRFuelEditor)super.getUserObject();
- }
-
- }
-
- public class PartNode extends DefaultMutableTreeNode implements PropertyChangeListener {
- private static final long serialVersionUID = 1L;
-
- public PartNode(Object part) {
- super(part, false);
- if (part instanceof ChangeListening.Subject) {
- ((ChangeListening.Subject) part).addPropertyChangeListener(this);
- }
- }
-
- @Override
- public void propertyChange(PropertyChangeEvent e) {
- nodeChanged(this);
- }
-
- }
-
- public class MotorNode extends PartNode implements PropertyChangeListener {
- private static final long serialVersionUID = 1L;
- Motor motor;
- PartNode cn, nn, gn, fn;
-
- public MotorNode(Motor m) {
- super(m);
- setAllowsChildren(true);
- motor = m;
- add( cn = new PartNode(m.getChamber()));
- add( nn = new PartNode(m.getNozzle()));
- if ( m.getGrain() instanceof MultiGrain ){
- gn = new MultiGrainNode(((MultiGrain)m.getGrain()));
- } else {
- gn = new PartNode(m.getGrain());
- }
- add(gn);
- if (m instanceof ChangeListening.Subject) {
- ((ChangeListening.Subject) m).addPropertyChangeListener(this);
- }
- }
-
- @Override
- public Motor getUserObject(){
- return (Motor)super.getUserObject();
- }
-
- @Override
- public void propertyChange(PropertyChangeEvent e) {
- nodeChanged(this);
- super.propertyChange(e);
- }
-
- }
-
- public WorkbenchTreeModel() {
- super(new DefaultMutableTreeNode("Root"), true);
- getRoot().add(motors);
- getRoot().add(fuel);
- }
-
- @Override
- public DefaultMutableTreeNode getRoot(){
- return (DefaultMutableTreeNode)super.getRoot();
- }
-
- public DefaultMutableTreeNode getMotors(){
- return motors;
- }
-
- public DefaultMutableTreeNode getFuels(){
- return fuel;
- }
-
- public void addMotor(Motor m){
- DefaultMutableTreeNode root = getRoot();
- motors.add(new MotorNode(m));
- nodesWereInserted(motors, new int[]{motors.getChildCount()-1});
-
- }
-
- @SuppressWarnings("unchecked")
- public void removeMotor(Motor m){
- Enumeration<TreeNode> e = motors.children();
- while ( e.hasMoreElements() ){
- TreeNode n = e.nextElement();
- if ( n instanceof MotorNode ){
- if ( ((MotorNode)n).getUserObject() == m ){
- removeNodeFromParent((MotorNode)n);
- }
- }
- }
- }
-
-}