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