<classpathentry kind="lib" path="jfreechart-1.0.12.jar"/>\r
<classpathentry kind="lib" path="jcommon-1.0.15.jar"/>\r
<classpathentry kind="lib" path="log4j-1.2.15.jar"/>\r
+ <classpathentry kind="lib" path="l2fprod-common-sheet.jar"/>\r
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>\r
<classpathentry kind="output" path="bin"/>\r
</classpath>\r
+++ /dev/null
-import javax.measure.quantity.Dimensionless;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.unit.SI;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-\r
-public class Test {\r
- public static void main(String args[]){\r
- Amount<Length> l = (Amount<Length>)Amount.valueOf(1, SI.METER);\r
- System.out.println(l.divide(l).to(Dimensionless.UNIT));\r
- Amount<Length> m = (Amount<Length>)Amount.valueOf(1, 10000000000000000.0, SI.METER);\r
- System.out.println(m);\r
- \r
- }\r
-}\r
import org.jscience.physics.amount.Constants;\r
\r
import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.grain.BurnPanel;\r
import com.billkuker.rocketry.motorsim.grain.CompoundGrain;\r
import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
+import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
\r
public class Burn {\r
\r
package com.billkuker.rocketry.motorsim;\r
\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Line2D;\r
+\r
import javax.measure.quantity.Area;\r
import javax.measure.quantity.Dimensionless;\r
import javax.measure.quantity.Force;\r
import javax.measure.quantity.Length;\r
import javax.measure.quantity.Pressure;\r
+import javax.measure.unit.SI;\r
\r
import org.jscience.physics.amount.Amount;\r
\r
-public class ConvergentDivergentNozzle implements Nozzle {\r
+public class ConvergentDivergentNozzle extends MotorPart implements Nozzle {\r
\r
private Amount<Length> throatDiameter;\r
\r
\r
\r
public void setThroatDiameter(Amount<Length> throatDiameter) {\r
+ if ( exitDiameter != null && throatDiameter.isGreaterThan(exitDiameter))\r
+ throw new IllegalArgumentException("Throat > Exit");\r
+ Amount<Length> old = this.throatDiameter;\r
this.throatDiameter = throatDiameter;\r
+ firePropertyChange("throatDiameter", old, throatDiameter);\r
}\r
\r
\r
\r
\r
public void setExitDiameter(Amount<Length> exitDiameter) {\r
+ if ( throatDiameter != null && exitDiameter.isLessThan(throatDiameter))\r
+ throw new IllegalArgumentException("Throat > Exit");\r
+ Amount<Length> old = this.exitDiameter;\r
this.exitDiameter = exitDiameter;\r
+ firePropertyChange("exitDiameter", old, exitDiameter);\r
}\r
\r
@Override\r
}\r
\r
public void setEfficiency(double efficiency) {\r
+ double old = this.efficiency;\r
this.efficiency = efficiency;\r
+ firePropertyChange("efficiency", old, efficiency);\r
}\r
\r
-\r
+ public Shape nozzleShape(Amount<Length> chamberDiameter){\r
+ GeneralPath s = new GeneralPath();\r
+ double throatR = throatDiameter.divide(2).doubleValue(SI.MILLIMETER);\r
+ double exitR = exitDiameter.divide(2).doubleValue(SI.MILLIMETER);\r
+ double cR;\r
+ if (chamberDiameter == null)\r
+ cR = exitR;\r
+ else\r
+ cR = chamberDiameter.divide(2).doubleValue(SI.MILLIMETER);\r
+ \r
+ double diff = exitR-throatR;\r
+ double cDiff = cR-throatR;\r
+ \r
+ s.append(new Line2D.Double(0,0,diff,diff*3), false);\r
+ s.append(new Line2D.Double(0,0,cDiff,-cDiff), true);\r
+ \r
+ s.transform(AffineTransform.getScaleInstance(-1, 1));\r
+ s.transform(AffineTransform.getTranslateInstance(-throatR, 0));\r
+ \r
+ s.append(new Line2D.Double(0,0,diff,diff*3), false);\r
+ s.append(new Line2D.Double(0,0,cDiff,-cDiff), true);\r
+ \r
+ //a.add(new java.awt.geom.Area( new Ellipse2D.Double(0,0,5,5)));\r
+ \r
+ return s;\r
+ }\r
}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim;\r
+\r
+import java.beans.BeanInfo;\r
+import java.beans.IntrospectionException;\r
+import java.beans.Introspector;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyChangeSupport;\r
+import java.beans.PropertyDescriptor;\r
+import java.beans.PropertyVetoException;\r
+import java.beans.VetoableChangeListener;\r
+import java.beans.VetoableChangeSupport;\r
+import java.lang.reflect.Field;\r
+import java.lang.reflect.ParameterizedType;\r
+import java.lang.reflect.Type;\r
+\r
+import javax.measure.quantity.Quantity;\r
+import javax.measure.unit.Unit;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+public class MotorPart {\r
+ PropertyChangeSupport pcs;\r
+ VetoableChangeSupport vcs;\r
+ \r
+ public interface Validating {\r
+ void checkValidity() throws ValidationException;\r
+ \r
+ public class ValidationException extends Exception {\r
+ private static final long serialVersionUID = 1L;\r
+ public ValidationException(Validating v, String error) {\r
+ super(v.toString() + ": " + error);\r
+ }\r
+ }\r
+ }\r
+ \r
+ public MotorPart() {\r
+ pcs = new PropertyChangeSupport(this);\r
+ vcs = new VetoableChangeSupport(this);\r
+ \r
+ vcs.addVetoableChangeListener(new VetoableChangeListener(){\r
+ @Override\r
+ public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {\r
+ if ( evt.getNewValue() instanceof Amount ){\r
+ try {\r
+ BeanInfo b = Introspector.getBeanInfo(evt.getSource().getClass());\r
+ PropertyDescriptor ps[] = b.getPropertyDescriptors();\r
+ for ( int i = 0; i < ps.length; i++ ){\r
+ if (ps[i].getName().equals(evt.getPropertyName())){\r
+ Type t = ps[i].getReadMethod().getGenericReturnType();\r
+ ParameterizedType p = (ParameterizedType) t;\r
+ Class expected = (Class) p.getActualTypeArguments()[0];\r
+ Field f = expected.getDeclaredField("UNIT");\r
+ Unit u = (Unit) f.get(null);\r
+ \r
+ Amount a = (Amount)evt.getNewValue();\r
+ if (!a.getUnit().isCompatible(u))\r
+ throw new PropertyVetoException(ps[i].getShortDescription()\r
+ + " must be in units of "\r
+ + expected.getSimpleName(), evt);\r
+\r
+ System.out.println("Expected " + expected + " got " + u);\r
+ }\r
+ }\r
+ } catch ( PropertyVetoException e ){\r
+ throw e;\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ public <T extends Quantity> void checkAmount(Amount<T> a, Class<T> q)\r
+ throws IllegalArgumentException {\r
+ try {\r
+ Field f = q.getDeclaredField("UNIT");\r
+ Unit<T> u = (Unit<T>)f.get(null);\r
+\r
+ if (!a.getUnit().isCompatible(u)) {\r
+ throw new IllegalArgumentException("Value "\r
+ + " must be in units of " + q.getSimpleName());\r
+ }\r
+ } catch (Exception e) {\r
+ throw new Error(e);\r
+ }\r
+ }\r
+\r
+ public void addPropertyChangeListener(PropertyChangeListener listener) {\r
+ pcs.addPropertyChangeListener(listener);\r
+ }\r
+\r
+ public void removePropertyChangeListener(PropertyChangeListener listener) {\r
+ pcs.removePropertyChangeListener(listener);\r
+ }\r
+\r
+ protected void firePropertyChange(PropertyChangeEvent evt) {\r
+ pcs.firePropertyChange(evt);\r
+ }\r
+\r
+ protected void firePropertyChange(String propertyName, boolean oldValue,\r
+ boolean newValue) {\r
+ pcs.firePropertyChange(propertyName, oldValue, newValue);\r
+ }\r
+\r
+ protected void firePropertyChange(String propertyName, int oldValue,\r
+ int newValue) {\r
+ pcs.firePropertyChange(propertyName, oldValue, newValue);\r
+ }\r
+\r
+ protected void firePropertyChange(String propertyName, Object oldValue,\r
+ Object newValue) {\r
+ pcs.firePropertyChange(propertyName, oldValue, newValue);\r
+ }\r
+\r
+ public void fireVetoableChange(String arg0, boolean arg1, boolean arg2)\r
+ throws PropertyVetoException {\r
+ vcs.fireVetoableChange(arg0, arg1, arg2);\r
+ }\r
+\r
+ public void fireVetoableChange(String arg0, int arg1, int arg2)\r
+ throws PropertyVetoException {\r
+ vcs.fireVetoableChange(arg0, arg1, arg2);\r
+ }\r
+\r
+ public void fireVetoableChange(String arg0, Object arg1, Object arg2)\r
+ throws PropertyVetoException {\r
+ vcs.fireVetoableChange(arg0, arg1, arg2);\r
+ }\r
+}\r
package com.billkuker.rocketry.motorsim;\r
\r
+import java.awt.Shape;\r
+\r
import javax.measure.quantity.Area;\r
import javax.measure.quantity.Force;\r
+import javax.measure.quantity.Length;\r
import javax.measure.quantity.Pressure;\r
\r
import org.jscience.physics.amount.Amount;\r
public Amount<Area> throatArea();\r
\r
public Amount<Force> thrust(Amount<Pressure> Po, Amount<Pressure> Pe, Amount<Pressure> Patm, final double k );\r
+ \r
+ public Shape nozzleShape(Amount<Length> chamberDiameter);\r
}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.grain;\r
-\r
-import java.awt.BorderLayout;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Duration;\r
-import javax.measure.quantity.Force;\r
-import javax.measure.quantity.Pressure;\r
-import javax.measure.quantity.Velocity;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.visual.Chart;\r
-\r
-public class BurnPanel extends JPanel {\r
- private Burn burn;\r
- Chart<Duration, Pressure> pressure;\r
- Chart<Duration, Force> thrust;\r
- Chart<Pressure, Velocity> burnRate;\r
- GrainPanel grain;\r
- Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
- \r
- public BurnPanel(Burn b){\r
- super( new BorderLayout() );\r
- burn = b;\r
- \r
- try {\r
- pressure = new Chart<Duration, Pressure>(\r
- SI.SECOND,\r
- SI.MEGA(SI.PASCAL),\r
- b,\r
- "pressure");\r
- pressure.setDomain(burn.getData().keySet());\r
- \r
- thrust = new Chart<Duration, Force>(\r
- SI.SECOND,\r
- SI.NEWTON,\r
- b,\r
- "thrust");\r
- thrust.setDomain(burn.getData().keySet());\r
- \r
- burnRate = new Chart<Pressure, Velocity>(\r
- SI.MEGA(SI.PASCAL),\r
- SI.METERS_PER_SECOND,\r
- burn.getMotor().getFuel(),\r
- "burnRate");\r
- burnRate.setDomain(\r
- burnRate.new IntervalDomain(\r
- Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
- Amount.valueOf(3, SI.MEGA(SI.PASCAL)),\r
- 20\r
- ));\r
- \r
- \r
- JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
- tp.setDividerLocation(.5);\r
- tp.setResizeWeight(.5);\r
- \r
- grain = new GrainPanel(burn.getMotor().getGrain());\r
- \r
- JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
- grains.setDividerLocation(.5);\r
- grains.setResizeWeight(.5);\r
- \r
- JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
- main.setDividerLocation(.5);\r
- main.setResizeWeight(.5);\r
- \r
- add( main, BorderLayout.CENTER );\r
- \r
- add( new SL(), BorderLayout.SOUTH);\r
- \r
- } catch (NoSuchMethodException e){\r
- throw new Error(e);\r
- }\r
- \r
-\r
- }\r
- \r
- private class SL extends JSlider implements ChangeListener{\r
- private static final long serialVersionUID = 1L;\r
- private static final int STEPS = 150;\r
- public SL(){\r
- addChangeListener(this);\r
- setMinimum(0);\r
- setMaximum(STEPS);\r
- setValue(0);\r
- }\r
- \r
- @Override\r
- public void stateChanged(ChangeEvent e) {\r
- double t = ((SL)e.getSource()).getValue();\r
- displayedTime = burn.burnTime().divide(STEPS).times(t);\r
- \r
- //Find the nearest key in the data set\r
- displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
- \r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- //System.out.println("Time: " + nf.format(displayedTime.doubleValue(SI.SECOND)) + "s");\r
- \r
- pressure.mark(displayedTime);\r
- thrust.mark(displayedTime);\r
- \r
- grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
- \r
- burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
- \r
- \r
- /*\r
- double r = ((SL)e.getSource()).getValue();\r
- displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
- area.mark(displayedRegression);\r
- volume.mark(displayedRegression);\r
- if ( xc != null )\r
- xc.repaint();\r
- */\r
- }\r
- }\r
- \r
- public void show(){\r
- JFrame f = new JFrame();\r
- f.setSize(1280,720);\r
- f.setLocation(0, 0);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
-}\r
import java.awt.Shape;\r
import java.awt.geom.Ellipse2D;\r
import java.awt.geom.Rectangle2D;\r
+import java.beans.PropertyVetoException;\r
\r
import javax.measure.quantity.Area;\r
import javax.measure.quantity.Length;\r
import org.jscience.physics.amount.Amount;\r
\r
import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.validation.Validating;\r
-import com.billkuker.rocketry.motorsim.validation.ValidationException;\r
+import com.billkuker.rocketry.motorsim.MotorPart;\r
\r
-public class CoredCylindricalGrain implements Grain, Validating {\r
+\r
+public class CoredCylindricalGrain extends MotorPart implements Grain, MotorPart.Validating {\r
\r
private Amount<Length> length, oD, iD;\r
- private boolean oInh = true, iInh = false, eInh = false;\r
+ private boolean outerSurfaceInhibited = true, innerSurfaceInhibited = false, endSurfaceInhibited = false;\r
\r
public CoredCylindricalGrain() {\r
-\r
+ length = Amount.valueOf(100, SI.MILLIMETER);\r
+ oD = Amount.valueOf(30, SI.MILLIMETER);\r
+ iD = Amount.valueOf(10, SI.MILLIMETER);\r
}\r
\r
+ \r
public void inhibit(boolean in, boolean out, boolean end){\r
- oInh = out;\r
- iInh = in;\r
- eInh = end;\r
+ outerSurfaceInhibited = out;\r
+ innerSurfaceInhibited = in;\r
+ endSurfaceInhibited = end;\r
}\r
\r
@Override\r
\r
//Calculated regressed length\r
Amount<Length> cLength = length;\r
- if ( !eInh ){\r
+ if ( !endSurfaceInhibited ){\r
cLength = cLength.minus(regression.times(2));\r
}\r
\r
//Calculate regressed iD\r
Amount<Length> cID = iD;\r
- if ( !iInh ){\r
+ if ( !innerSurfaceInhibited ){\r
cID = iD.plus(regression.times(2));\r
}\r
\r
//Calculate regressed oD\r
Amount<Length> cOD = oD;\r
- if ( !oInh ){\r
+ if ( !outerSurfaceInhibited ){\r
cOD = oD.minus(regression.times(2));\r
}\r
\r
\r
Amount<Area> ends = (cOD.divide(2).pow(2).times(Math.PI)).minus(cID.divide(2).pow(2).times(Math.PI)).times(2).to(SI.SQUARE_METRE);\r
\r
- Amount<Area> total = inner.times(iInh?0:1).plus(outer.times(oInh?0:1)).plus(ends.times(eInh?0:1));\r
+ Amount<Area> total = inner.times(innerSurfaceInhibited?0:1).plus(outer.times(outerSurfaceInhibited?0:1)).plus(ends.times(endSurfaceInhibited?0:1));\r
\r
return total;\r
}\r
\r
//Calculated regressed length\r
Amount<Length> cLength = length;\r
- if ( !eInh ){\r
+ if ( !endSurfaceInhibited ){\r
cLength = cLength.minus(regression.times(2));\r
}\r
\r
//Calculate regressed iD\r
Amount<Length> cID = iD;\r
- if ( !iInh ){\r
+ if ( !innerSurfaceInhibited ){\r
cID = iD.plus(regression.times(2));\r
}\r
\r
//Calculate regressed oD\r
Amount<Length> cOD = oD;\r
- if ( !oInh ){\r
+ if ( !outerSurfaceInhibited ){\r
cOD = oD.minus(regression.times(2));\r
}\r
\r
return end.times(cLength).to(SI.CUBIC_METRE);\r
}\r
\r
- public void setLength(Amount<Length> length) {\r
+ public void setLength(Amount<Length> length) throws PropertyVetoException {\r
+ fireVetoableChange("length", this.length, length);\r
+ Amount<Length> old = this.length;\r
this.length = length;\r
+ firePropertyChange("length", old, length);\r
}\r
\r
- public void setOD(Amount<Length> od) {\r
- oD = od;\r
+ public void setOD(Amount<Length> od) throws PropertyVetoException {\r
+ fireVetoableChange("od", this.oD, od);\r
+ Amount<Length> old = this.oD;\r
+ this.oD = od;\r
+ firePropertyChange("OD", old, oD);\r
}\r
\r
- public void setID(Amount<Length> id) {\r
+ public void setID(Amount<Length> id) throws PropertyVetoException {\r
+ fireVetoableChange("id", this.iD, id);\r
+ Amount<Length> old = this.iD;\r
iD = id;\r
+ firePropertyChange("ID", old, iD);\r
}\r
\r
public void checkValidity() throws ValidationException{\r
if ( iD.isGreaterThan(oD) )\r
throw new ValidationException(this, "iD > oD");\r
\r
- if ( iInh && oInh && eInh )\r
+ if ( innerSurfaceInhibited && outerSurfaceInhibited && endSurfaceInhibited )\r
throw new ValidationException(this, "No exposed grain surface");\r
\r
}\r
\r
@Override\r
public Amount<Length> webThickness() {\r
+ if ( innerSurfaceInhibited && outerSurfaceInhibited && endSurfaceInhibited ){\r
+ return oD;\r
+ }\r
+ \r
Amount<Length> radial = null;\r
- if ( !iInh && !oInh )\r
+ if ( !innerSurfaceInhibited && !outerSurfaceInhibited )\r
radial = oD.minus(iD).divide(4); //Outer and inner exposed\r
- else if ( !iInh || !oInh )\r
+ else if ( !innerSurfaceInhibited || !outerSurfaceInhibited )\r
radial = oD.minus(iD).divide(2); //Outer or inner exposed\r
\r
Amount<Length> axial = null;\r
\r
- if ( !eInh )\r
+ if ( !endSurfaceInhibited )\r
axial = length.divide(2);\r
\r
if ( axial == null )\r
double oDmm = oD.doubleValue(SI.MILLIMETER);\r
double iDmm = iD.doubleValue(SI.MILLIMETER);\r
\r
- if ( !oInh )\r
+ if ( !outerSurfaceInhibited )\r
oDmm -= 2.0 * rmm;\r
- if ( !iInh )\r
+ if ( !innerSurfaceInhibited )\r
iDmm += 2.0 * rmm;\r
\r
Shape oDs = new Ellipse2D.Double(-oDmm/2.0, -oDmm/2.0, oDmm, oDmm);\r
double iDmm = iD.doubleValue(SI.MILLIMETER);\r
double lmm = length.doubleValue(SI.MILLIMETER);\r
\r
- if ( !oInh )\r
+ if ( !outerSurfaceInhibited )\r
oDmm -= 2.0 * rmm;\r
- if ( !iInh )\r
+ if ( !innerSurfaceInhibited )\r
iDmm += 2.0 * rmm;\r
- if ( !eInh )\r
+ if ( !endSurfaceInhibited )\r
lmm -= 2.0 * rmm;\r
\r
java.awt.geom.Area a = new java.awt.geom.Area();\r
\r
return a;\r
}\r
+\r
+\r
+ public boolean isOuterSurfaceInhibited() {\r
+ return outerSurfaceInhibited;\r
+ }\r
+\r
+\r
+ public void setOuterSurfaceInhibited(boolean outerSurfaceInhibited) throws PropertyVetoException {\r
+ fireVetoableChange("outerSurfaceInhibited", this.outerSurfaceInhibited, outerSurfaceInhibited);\r
+ boolean old = this.outerSurfaceInhibited;\r
+ this.outerSurfaceInhibited = outerSurfaceInhibited;\r
+ firePropertyChange("outerSurfaceInhibited", old, outerSurfaceInhibited);\r
+ }\r
+\r
+\r
+ public boolean isInnerSurfaceInhibited() {\r
+ return innerSurfaceInhibited;\r
+ }\r
+\r
+\r
+ public void setInnerSurfaceInhibited(boolean innerSurfaceInhibited) throws PropertyVetoException {\r
+ fireVetoableChange("innerSurfaceInhibited", this.innerSurfaceInhibited, innerSurfaceInhibited);\r
+ boolean old = this.innerSurfaceInhibited;\r
+ this.innerSurfaceInhibited = innerSurfaceInhibited;\r
+ firePropertyChange("innerSurfaceInhibited", old, innerSurfaceInhibited);\r
+ }\r
+\r
+\r
+ public boolean isEndSurfaceInhibited() {\r
+ return endSurfaceInhibited;\r
+ }\r
+\r
+\r
+ public void setEndSurfaceInhibited(boolean endSurfaceInhibited) throws PropertyVetoException {\r
+ fireVetoableChange("endSurfaceInhibited", this.endSurfaceInhibited, endSurfaceInhibited);\r
+ boolean old = this.endSurfaceInhibited;\r
+ this.endSurfaceInhibited = endSurfaceInhibited;\r
+ firePropertyChange("endSurfaceInhibited", old, endSurfaceInhibited);\r
+ }\r
\r
\r
}\r
import org.jscience.physics.amount.Amount;\r
\r
import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
\r
public class ExtrudedGrain implements Grain {\r
\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.grain;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Dimension;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.geom.AffineTransform;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.visual.Chart;\r
-\r
-public class GrainPanel extends JPanel {\r
- private static final long serialVersionUID = 1L;\r
- private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
- private JLabel l = new JLabel();\r
- private Chart<Length,Area> area;\r
- Chart<Length, Volume> volume;\r
- private XC xc;\r
- private SL sl;\r
- private Grain grain;\r
- \r
- public GrainPanel(Grain g){\r
- super(new BorderLayout());\r
-\r
- grain = g;\r
-\r
- try {\r
-\r
- area = new Chart<Length, Area>(\r
- SI.MILLIMETER,\r
- SI.MILLIMETER.pow(2).asType(Area.class),\r
- grain,\r
- "surfaceArea");\r
- area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
- \r
- volume = new Chart<Length, Volume>(\r
- SI.MILLIMETER,\r
- SI.MILLIMETER.pow(3).asType(Volume.class),\r
- grain,\r
- "volume");\r
- volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-\r
- area.setMaximumSize(new Dimension(200,100));\r
- volume.setMaximumSize(new Dimension(200,100));\r
- \r
-\r
- } catch (ClassCastException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- } catch (NoSuchMethodException e) {\r
- // TODO Auto-generated catch block\r
- e.printStackTrace();\r
- }\r
- \r
- \r
- JSplitPane charts = new JSplitPane(JSplitPane.VERTICAL_SPLIT, area, volume);\r
- charts.setDividerLocation(.5);\r
- charts.setResizeWeight(.5);\r
- \r
- JPanel left = new JPanel(new BorderLayout());\r
- \r
- add(xc = new XC(grain), BorderLayout.CENTER);\r
- left.add(xc);\r
-\r
- left.add(l, BorderLayout.NORTH);\r
- left.add( sl = new SL(), BorderLayout.SOUTH);\r
- \r
- add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, charts));\r
-\r
- }\r
- \r
- public void setDisplayedRegression( Amount<Length> r ){\r
- displayedRegression = r;\r
- \r
- NumberFormat nf = NumberFormat.getInstance();\r
- nf.setMaximumFractionDigits(2);\r
- l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
- \r
- area.mark(displayedRegression);\r
- volume.mark(displayedRegression);\r
- if ( xc != null )\r
- xc.repaint();\r
- }\r
- \r
- private class XC extends JPanel{\r
- private static final long serialVersionUID = 1L;\r
- Grain grain;\r
- public XC(Grain g){\r
- setMinimumSize(new Dimension(440,250));\r
- grain = g;\r
- }\r
- public void paint(Graphics g){\r
- super.paint(g);\r
- Graphics2D g2d = (Graphics2D)g;\r
- g2d.translate(10, 30);\r
- /*\r
- grain.draw(g2d, displayedRegression );\r
- */\r
- \r
- {\r
- AffineTransform t = g2d.getTransform();\r
- java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
- \r
- Rectangle bounds = unburnt.getBounds();\r
- g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
- g2d.translate(-bounds.getX(), -bounds.getY());\r
- \r
- //Draw the fuel that is left\r
- java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
- g2d.setColor(Color.RED);\r
- g2d.fill(burning);\r
- //Draw the fuel that is left\r
- java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
- g2d.setColor(Color.GRAY);\r
- g2d.fill(left);\r
- //Draw the outline of the unburnt grain\r
- g2d.setColor(Color.BLACK);\r
- g2d.draw(unburnt);\r
- //untranslate\r
- g2d.setTransform(t);\r
- }\r
- {\r
- AffineTransform t = g2d.getTransform();\r
- java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
- \r
- Rectangle bounds = unburnt.getBounds();\r
- g2d.translate(220, 0);\r
- \r
- double max = bounds.getWidth();\r
- if ( bounds.getHeight() > max )\r
- max = bounds.getHeight();\r
- \r
- g2d.scale(200 / max, 200 / max);\r
- g2d.translate(-bounds.getX(), -bounds.getY());\r
- \r
- //Draw the fuel that is left\r
- java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
- g2d.setColor(Color.RED);\r
- g2d.fill(burning);\r
- //Draw the fuel that is left\r
- java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
- g2d.setColor(Color.GRAY);\r
- g2d.fill(left);\r
- //Draw the outline of the unburnt grain\r
- g2d.setColor(Color.BLACK);\r
- g2d.draw(unburnt);\r
- //untranslate\r
- g2d.setTransform(t);\r
- }\r
- \r
- }\r
- }\r
- \r
- private class SL extends JSlider implements ChangeListener{\r
- private static final long serialVersionUID = 1L;\r
- private static final int STEPS = 60;\r
- public SL(){\r
- addChangeListener(this);\r
- setMinimum(0);\r
- setMaximum(STEPS);\r
- setValue(0);\r
- }\r
- \r
- @Override\r
- public void stateChanged(ChangeEvent e) {\r
- double r = ((SL)e.getSource()).getValue();\r
-\r
- setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
- }\r
- }\r
- \r
- public void show(){\r
- JFrame f = new JFrame();\r
- f.setSize(1024,600);\r
- f.setContentPane(this);\r
- f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
- f.setVisible(true);\r
- }\r
-\r
-}\r
package com.billkuker.rocketry.motorsim.test;\r
\r
+import java.beans.PropertyVetoException;\r
+\r
import javax.measure.quantity.Length;\r
import javax.measure.unit.SI;\r
\r
import org.jscience.physics.amount.Amount;\r
import org.junit.Test;\r
\r
+import com.billkuker.rocketry.motorsim.MotorPart;\r
import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
-import com.billkuker.rocketry.motorsim.validation.ValidationException;\r
\r
public class CoredCylindricalGrainTest extends RocketTest {\r
\r
@Test\r
- public void testSurfaceArea() {\r
+ public void testSurfaceArea() throws PropertyVetoException {\r
CoredCylindricalGrain g = new CoredCylindricalGrain();\r
\r
g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
\r
\r
@Test\r
- public void testWebThickness() {\r
+ public void testWebThickness() throws PropertyVetoException {\r
CoredCylindricalGrain g = new CoredCylindricalGrain();\r
\r
//thin and long\r
}\r
\r
@Test\r
- public void testVolume() {\r
+ public void testVolume() throws PropertyVetoException {\r
CoredCylindricalGrain g = new CoredCylindricalGrain();\r
\r
//thin and long\r
System.out.println(g.volume(Amount.valueOf(0, SI.MILLIMETER)));\r
}\r
\r
- @Test(expected=ValidationException.class)\r
- public void testCheckValidity() throws ValidationException{\r
+ @Test(expected=MotorPart.Validating.ValidationException.class)\r
+ public void testCheckValidity() throws MotorPart.Validating.ValidationException, PropertyVetoException{\r
CoredCylindricalGrain g = new CoredCylindricalGrain();\r
\r
//thin and long\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.validation;\r
-\r
-public interface Validating {\r
- void checkValidity() throws ValidationException;\r
-}\r
+++ /dev/null
-package com.billkuker.rocketry.motorsim.validation;\r
-\r
-public class ValidationException extends Exception {\r
- /**\r
- * \r
- */\r
- private static final long serialVersionUID = 1L;\r
-\r
- public ValidationException(Validating v, String error) {\r
- super(v.toString() + ": " + error);\r
- }\r
-}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Duration;\r
+import javax.measure.quantity.Force;\r
+import javax.measure.quantity.Pressure;\r
+import javax.measure.quantity.Velocity;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+\r
+public class BurnPanel extends JPanel {\r
+ private Burn burn;\r
+ Chart<Duration, Pressure> pressure;\r
+ Chart<Duration, Force> thrust;\r
+ Chart<Pressure, Velocity> burnRate;\r
+ GrainPanel grain;\r
+ Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
+ \r
+ public BurnPanel(Burn b){\r
+ super( new BorderLayout() );\r
+ burn = b;\r
+ \r
+ try {\r
+ pressure = new Chart<Duration, Pressure>(\r
+ SI.SECOND,\r
+ SI.MEGA(SI.PASCAL),\r
+ b,\r
+ "pressure");\r
+ pressure.setDomain(burn.getData().keySet());\r
+ \r
+ thrust = new Chart<Duration, Force>(\r
+ SI.SECOND,\r
+ SI.NEWTON,\r
+ b,\r
+ "thrust");\r
+ thrust.setDomain(burn.getData().keySet());\r
+ \r
+ burnRate = new Chart<Pressure, Velocity>(\r
+ SI.MEGA(SI.PASCAL),\r
+ SI.METERS_PER_SECOND,\r
+ burn.getMotor().getFuel(),\r
+ "burnRate");\r
+ burnRate.setDomain(\r
+ burnRate.new IntervalDomain(\r
+ Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
+ Amount.valueOf(3, SI.MEGA(SI.PASCAL)),\r
+ 20\r
+ ));\r
+ \r
+ \r
+ JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
+ tp.setDividerLocation(.5);\r
+ tp.setResizeWeight(.5);\r
+ \r
+ grain = new GrainPanel(burn.getMotor().getGrain());\r
+ \r
+ JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
+ grains.setDividerLocation(.5);\r
+ grains.setResizeWeight(.5);\r
+ \r
+ JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
+ main.setDividerLocation(.5);\r
+ main.setResizeWeight(.5);\r
+ \r
+ add( main, BorderLayout.CENTER );\r
+ \r
+ add( new SL(), BorderLayout.SOUTH);\r
+ \r
+ } catch (NoSuchMethodException e){\r
+ throw new Error(e);\r
+ }\r
+ \r
+\r
+ }\r
+ \r
+ private class SL extends JSlider implements ChangeListener{\r
+ private static final long serialVersionUID = 1L;\r
+ private static final int STEPS = 150;\r
+ public SL(){\r
+ addChangeListener(this);\r
+ setMinimum(0);\r
+ setMaximum(STEPS);\r
+ setValue(0);\r
+ }\r
+ \r
+ @Override\r
+ public void stateChanged(ChangeEvent e) {\r
+ double t = ((SL)e.getSource()).getValue();\r
+ displayedTime = burn.burnTime().divide(STEPS).times(t);\r
+ \r
+ //Find the nearest key in the data set\r
+ displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
+ \r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ //System.out.println("Time: " + nf.format(displayedTime.doubleValue(SI.SECOND)) + "s");\r
+ \r
+ pressure.mark(displayedTime);\r
+ thrust.mark(displayedTime);\r
+ \r
+ grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
+ \r
+ burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
+ \r
+ \r
+ /*\r
+ double r = ((SL)e.getSource()).getValue();\r
+ displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+ area.mark(displayedRegression);\r
+ volume.mark(displayedRegression);\r
+ if ( xc != null )\r
+ xc.repaint();\r
+ */\r
+ }\r
+ }\r
+ \r
+ public void show(){\r
+ JFrame f = new JFrame();\r
+ f.setSize(1280,720);\r
+ f.setLocation(0, 0);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+}\r
import org.jfree.data.xy.XYSeriesCollection;\r
import org.jscience.physics.amount.Amount;\r
\r
+import sun.misc.Cleaner;\r
+\r
import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
\r
public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
//double step = (high - low) / 50;\r
//for (double x = low; x < high; x += step) {\r
//Amount<X> ax = Amount.valueOf(x, xUnit);\r
+ series.clear();\r
for( Amount<X> ax: d){\r
try {\r
Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.GridLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.FocusEvent;\r
+import java.awt.event.FocusListener;\r
+import java.awt.event.InputEvent;\r
+import java.awt.event.ItemEvent;\r
+import java.awt.event.ItemListener;\r
+import java.beans.BeanInfo;\r
+import java.beans.IntrospectionException;\r
+import java.beans.Introspector;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyDescriptor;\r
+import java.beans.PropertyEditor;\r
+import java.beans.PropertyEditorManager;\r
+import java.beans.PropertyEditorSupport;\r
+import java.beans.PropertyVetoException;\r
+import java.lang.reflect.Field;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.lang.reflect.ParameterizedType;\r
+import java.lang.reflect.Type;\r
+\r
+import javax.measure.unit.NonSI;\r
+import javax.measure.unit.Unit;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JTextField;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
+import com.l2fprod.common.propertysheet.PropertySheetPanel;\r
+import com.sun.media.sound.Toolkit;\r
+\r
+public class Editor extends JPanel {\r
+ private static Logger log = Logger.getLogger(Editor.class);\r
+\r
+ private Object obj;\r
+\r
+ public Editor(Object o) throws IntrospectionException,\r
+ IllegalArgumentException, IllegalAccessException,\r
+ InvocationTargetException {\r
+ obj = o;\r
+ /*\r
+ BeanInfo b = Introspector.getBeanInfo(o.getClass());\r
+ setLayout(new GridLayout(b.getPropertyDescriptors().length - 1, 2));\r
+ for (PropertyDescriptor p : b.getPropertyDescriptors()) {\r
+ log.debug(p.getName());\r
+ if (p.getName().equals("class"))\r
+ continue;\r
+\r
+ add(new JLabel(p.getName()));\r
+ if (p.getPropertyType().isAssignableFrom(Amount.class)){\r
+ add(new AmountEditor(p));\r
+ } else if ( p.getPropertyType() == boolean.class ){\r
+ add( new BooleanEditor(p) );\r
+ \r
+ }\r
+ }\r
+ log.debug(PropertyEditorManager.findEditor(boolean.class).supportsCustomEditor());*/\r
+ \r
+ PropertyEditorManager.registerEditor(Amount.class, AmountPropertyEditor.class);\r
+ \r
+ final PropertySheetPanel ps = new PropertySheetPanel();\r
+ ps.setBeanInfo(Introspector.getBeanInfo(obj.getClass()));\r
+ ps.readFromObject(obj);\r
+ add(ps);\r
+ \r
+ ps.addPropertySheetChangeListener(new PropertyChangeListener(){\r
+\r
+ @Override\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ //When something changes just update the\r
+ //object, I want the changes to be immediate.\r
+ try {\r
+ ps.writeToObject(obj);\r
+ } catch ( Exception v ){\r
+ //TODO\r
+ v.printStackTrace();\r
+ java.awt.Toolkit.getDefaultToolkit().beep();\r
+ } finally {\r
+ ps.readFromObject(obj);\r
+ }\r
+ }\r
+ \r
+ });\r
+ }\r
+\r
+ public static void main(String args[]) throws Exception {\r
+ CoredCylindricalGrain g = new CoredCylindricalGrain();\r
+ g.setOD(Amount.valueOf(1, NonSI.INCH));\r
+ new Editor(g).show();\r
+ \r
+ new GrainPanel(g).show();\r
+ }\r
+\r
+ public void show() {\r
+ JFrame f = new JFrame();\r
+ f.setSize(600,400);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+ \r
+ public static class AmountPropertyEditor extends PropertyEditorSupport{\r
+ JTextField editor = new JTextField();\r
+ Unit oldUnit;\r
+\r
+ @Override\r
+ public boolean supportsCustomEditor() {\r
+ return true;\r
+ }\r
+ @Override public String getAsText(){\r
+ return editor.getText();\r
+ }\r
+ \r
+ @Override public Object getValue(){\r
+ String text = editor.getText().trim();\r
+ try {\r
+ try {\r
+ return Amount.valueOf(Integer.parseInt(text), oldUnit);\r
+ } catch (NumberFormatException e){\r
+ \r
+ }\r
+ Amount a = Amount.valueOf(Double.parseDouble(text), oldUnit);\r
+ return a;\r
+ } catch (NumberFormatException e){\r
+ Amount a = Amount.valueOf(editor.getText());\r
+ oldUnit = a.getUnit();\r
+ return a;\r
+ }\r
+\r
+ }\r
+ @Override public void setValue(Object o){\r
+ Amount a = (Amount)o;\r
+ oldUnit = a.getUnit();\r
+ \r
+ String text;\r
+ if ( a.isExact() )\r
+ text = a.getExactValue() + " " + a.getUnit();\r
+ else\r
+ text = a.doubleValue(a.getUnit()) + " " + a.getUnit();\r
+ \r
+ setAsText(text);\r
+ }\r
+ @Override public void setAsText(String text) throws IllegalArgumentException {\r
+ editor.setText(text);\r
+ };\r
+ @Override\r
+ public Component getCustomEditor(){\r
+ return editor;\r
+ }\r
+ }\r
+\r
+\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Dimension;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.geom.AffineTransform;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.MotorPart;\r
+\r
+public class GrainPanel extends JPanel {\r
+ private static final long serialVersionUID = 1L;\r
+ private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
+ private JLabel l = new JLabel();\r
+ private Chart<Length,Area> area;\r
+ Chart<Length, Volume> volume;\r
+ private XC xc;\r
+ private SL sl;\r
+ private Grain grain;\r
+ \r
+ public GrainPanel(Grain g){\r
+ super(new BorderLayout());\r
+\r
+ grain = g;\r
+ \r
+ if ( g instanceof MotorPart ){\r
+ ((MotorPart)g).addPropertyChangeListener(new PropertyChangeListener(){\r
+ @Override\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ repaint();\r
+ area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ }\r
+ });\r
+ }\r
+\r
+\r
+ try {\r
+\r
+ area = new Chart<Length, Area>(\r
+ SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(2).asType(Area.class),\r
+ grain,\r
+ "surfaceArea");\r
+ area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+ \r
+ volume = new Chart<Length, Volume>(\r
+ SI.MILLIMETER,\r
+ SI.MILLIMETER.pow(3).asType(Volume.class),\r
+ grain,\r
+ "volume");\r
+ volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+\r
+ area.setMaximumSize(new Dimension(200,100));\r
+ volume.setMaximumSize(new Dimension(200,100));\r
+ \r
+\r
+ } catch (ClassCastException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (NoSuchMethodException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ \r
+ \r
+ JSplitPane charts = new JSplitPane(JSplitPane.VERTICAL_SPLIT, area, volume);\r
+ charts.setDividerLocation(.5);\r
+ charts.setResizeWeight(.5);\r
+ \r
+ JPanel left = new JPanel(new BorderLayout());\r
+ \r
+ add(xc = new XC(grain), BorderLayout.CENTER);\r
+ left.add(xc);\r
+\r
+ left.add(l, BorderLayout.NORTH);\r
+ left.add( sl = new SL(), BorderLayout.SOUTH);\r
+ \r
+ add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, charts));\r
+\r
+ }\r
+ \r
+ public void setDisplayedRegression( Amount<Length> r ){\r
+ displayedRegression = r;\r
+ \r
+ NumberFormat nf = NumberFormat.getInstance();\r
+ nf.setMaximumFractionDigits(2);\r
+ l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+ \r
+ area.mark(displayedRegression);\r
+ volume.mark(displayedRegression);\r
+ if ( xc != null )\r
+ xc.repaint();\r
+ }\r
+ \r
+ private class XC extends JPanel{\r
+ private static final long serialVersionUID = 1L;\r
+ Grain grain;\r
+ public XC(Grain g){\r
+ setMinimumSize(new Dimension(440,250));\r
+ grain = g;\r
+ }\r
+ public void paint(Graphics g){\r
+ super.paint(g);\r
+ Graphics2D g2d = (Graphics2D)g;\r
+ g2d.translate(10, 30);\r
+ /*\r
+ grain.draw(g2d, displayedRegression );\r
+ */\r
+ \r
+ {\r
+ AffineTransform t = g2d.getTransform();\r
+ java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = unburnt.getBounds();\r
+ g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
+ g2d.translate(-bounds.getX(), -bounds.getY());\r
+ \r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
+ g2d.setColor(Color.RED);\r
+ g2d.fill(burning);\r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
+ g2d.setColor(Color.GRAY);\r
+ g2d.fill(left);\r
+ //Draw the outline of the unburnt grain\r
+ g2d.setColor(Color.BLACK);\r
+ g2d.draw(unburnt);\r
+ //untranslate\r
+ g2d.setTransform(t);\r
+ }\r
+ {\r
+ AffineTransform t = g2d.getTransform();\r
+ java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = unburnt.getBounds();\r
+ g2d.translate(220, 0);\r
+ \r
+ double max = bounds.getWidth();\r
+ if ( bounds.getHeight() > max )\r
+ max = bounds.getHeight();\r
+ \r
+ g2d.scale(200 / max, 200 / max);\r
+ g2d.translate(-bounds.getX(), -bounds.getY());\r
+ \r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
+ g2d.setColor(Color.RED);\r
+ g2d.fill(burning);\r
+ //Draw the fuel that is left\r
+ java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
+ g2d.setColor(Color.GRAY);\r
+ g2d.fill(left);\r
+ //Draw the outline of the unburnt grain\r
+ g2d.setColor(Color.BLACK);\r
+ g2d.draw(unburnt);\r
+ //untranslate\r
+ g2d.setTransform(t);\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ private class SL extends JSlider implements ChangeListener{\r
+ private static final long serialVersionUID = 1L;\r
+ private static final int STEPS = 60;\r
+ public SL(){\r
+ addChangeListener(this);\r
+ setMinimum(0);\r
+ setMaximum(STEPS);\r
+ setValue(0);\r
+ }\r
+ \r
+ @Override\r
+ public void stateChanged(ChangeEvent e) {\r
+ double r = ((SL)e.getSource()).getValue();\r
+\r
+ setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
+ }\r
+ }\r
+ \r
+ public void show(){\r
+ JFrame f = new JFrame();\r
+ f.setSize(1024,600);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.Area;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.text.EditorKit;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
+import com.billkuker.rocketry.motorsim.MotorPart;\r
+import com.billkuker.rocketry.motorsim.Nozzle;\r
+import com.billkuker.rocketry.motorsim.visual.Chart.IntervalDomain;\r
+\r
+public class NozzlePanel extends JPanel {\r
+ private Nozzle nozzle;\r
+ public NozzlePanel(Nozzle n){\r
+ nozzle = n;\r
+ \r
+ if ( n instanceof MotorPart ){\r
+ ((MotorPart)n).addPropertyChangeListener(new PropertyChangeListener(){\r
+ @Override\r
+ public void propertyChange(PropertyChangeEvent evt) {\r
+ repaint();\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ public void paint(Graphics g){\r
+ super.paint(g);\r
+ Graphics2D g2d = (Graphics2D)g;\r
+ g2d.translate(10, 10);\r
+\r
+ g2d.setColor(Color.black);\r
+ \r
+ \r
+ Shape a = nozzle.nozzleShape(Amount.valueOf(30, SI.MILLIMETER));\r
+ \r
+ Rectangle bounds = a.getBounds();\r
+ double max = bounds.getWidth();\r
+ if ( bounds.getHeight() > max )\r
+ max = bounds.getHeight();\r
+ \r
+ g2d.scale(200 / max, 200 / max);\r
+ g2d.translate(-bounds.getX(), -bounds.getY());\r
+ \r
+ g2d.setStroke(new BasicStroke(4*(float)max/200f));\r
+ \r
+ g2d.draw( a );\r
+ }\r
+ \r
+ public void show(){\r
+ JFrame f = new JFrame();\r
+ f.setSize(220,250);\r
+ f.setContentPane(this);\r
+ f.setDefaultCloseOperation(f.DISPOSE_ON_CLOSE);\r
+ f.setVisible(true);\r
+ }\r
+ \r
+ public static void main(String args[]) throws Exception{\r
+ ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
+ n.setThroatDiameter(Amount.valueOf(5, SI.MILLIMETER));\r
+ n.setExitDiameter(Amount.valueOf(9, SI.MILLIMETER));\r
+ new Editor(n).show();\r
+ new NozzlePanel(n).show();\r
+ }\r
+}\r