--- /dev/null
+package com.billkuker.rocketry.motorsim;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.measure.quantity.Area;
+import javax.measure.quantity.Length;
+import javax.measure.quantity.Quantity;
+import javax.measure.unit.SI;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;
+import com.billkuker.rocketry.motorsim.visual.Chart;
+
+public class GraphSimplifier<X extends Quantity, Y extends Quantity> {
+ Method f;
+
+ private class Entry {
+ Amount<X> x;
+ Amount<Y> y;
+ }
+
+ private Map<Amount<X>, Amount<Y>> out = new HashMap<Amount<X>, Amount<Y>>();
+ private List<Amount<X>> outDomain = new Vector<Amount<X>>();
+
+ public Amount<Y> value(Amount<X> x) {
+ return out.get(x);
+ }
+
+ public Iterable<Amount<X>> getDomain() {
+ return outDomain;
+ }
+
+ public GraphSimplifier(Object source, String method,
+ Iterator<Amount<X>> domain) throws NoSuchMethodException,
+ IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException {
+ f = source.getClass().getMethod(method, Amount.class);
+
+ Vector<Entry> oldEntries = new Vector<Entry>();
+
+ Amount<Y> max = null, min = null, range = null, err = null;
+ while (domain.hasNext()) {
+ Amount<X> x = domain.next();
+ Amount<Y> y = (Amount<Y>) f.invoke(source, x);
+ Entry e = new Entry();
+ e.x = x;
+ e.y = y;
+ oldEntries.add(e);
+
+ if (max == null || max.isLessThan(y))
+ max = y;
+ if (min == null || min.isGreaterThan(y))
+ min = y;
+ }
+ range = max.minus(min).abs();
+ err = range.divide(50);
+
+ for (int j = 0; j < 10 && oldEntries.size() > 30; j++) {
+ Vector<Entry> newEntries = new Vector<Entry>();
+ newEntries.add(oldEntries.firstElement());
+ for (int i = 1; i < oldEntries.size() - 1; i = i + 2) {
+ Entry low = oldEntries.elementAt(i - 1);
+ Entry middle = oldEntries.elementAt(i);
+ Entry high = oldEntries.elementAt(i + 1);
+
+ Amount<X> dx = high.x.minus(low.x);
+ Amount<Y> dy = high.y.minus(low.y);
+
+ Amount<X> ddx = middle.x.minus(low.x);
+ Amount<Y> lin = low.y.plus(dy.times(ddx.divide(dx)));
+
+ Amount<Y> dif = middle.y.minus(lin).abs();
+
+ // System.out.println(middle.x + "\t" + middle.y + "\t" + lin +
+ // "\t" + dif + "\t"
+ // + dy);
+
+ if (dif.isGreaterThan(err)) {
+ newEntries.add(middle);
+ }
+ newEntries.add(high);
+ }
+ newEntries.add(oldEntries.lastElement());
+
+ System.out.println("Size " + oldEntries.size() + " -> "
+ + newEntries.size());
+
+ oldEntries = newEntries;
+ }
+
+ for (Entry ee : oldEntries) {
+ if (out.get(ee.x) == null) {
+ out.put(ee.x, ee.y);
+ outDomain.add(ee.x);
+ }
+ }
+ }
+
+ 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<Length, Area> c = new Chart<Length, Area>(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();
+
+ GraphSimplifier<Length, Area> gs = new GraphSimplifier(g,
+ "surfaceArea", c.new IntervalDomain(Amount.valueOf(0,
+ SI.CENTIMETER), g.webThickness()).iterator());
+
+ Chart<Length, Area> d = new Chart<Length, Area>(SI.MILLIMETER,
+ SI.MILLIMETER.pow(2).asType(Area.class), gs, "value");
+ d.setDomain(gs.getDomain());
+ d.show();
+ }
+}
--- /dev/null
+package com.billkuker.rocketry.motorsim.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.measure.quantity.Duration;
+import javax.measure.quantity.Force;
+import javax.measure.quantity.Mass;
+import javax.measure.unit.SI;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Burn;
+import com.billkuker.rocketry.motorsim.CylindricalChamber;
+import com.billkuker.rocketry.motorsim.GraphSimplifier;
+
+public class ENGExporter {
+
+ public static void export(Iterable<Burn> bb, File f) throws IOException {
+ export(bb, new FileOutputStream(f));
+ }
+
+ public static void export(Iterable<Burn> bb, OutputStream os) throws IOException {
+ for (Burn b : bb) {
+ export(b, os);
+ }
+ }
+
+ public static void export(Burn b, OutputStream os) throws IOException {
+
+ CylindricalChamber cha = (CylindricalChamber) b.getMotor().getChamber();
+
+ NumberFormat nf = new DecimalFormat("00.###");
+
+ StringBuffer out = new StringBuffer();
+
+ out.append(";Output from Motorsim, motorsim@billkuker.com\n");
+ out.append(";You must fill in Delays and Total Weight\n");
+ out.append(";Name Diameter Length Delays ProWt Wt Manufacturer\n");
+ out.append(b.getMotor().getName().replace(" ", "-") + " ");
+
+ double dia = cha.getOD().doubleValue(SI.MILLIMETER);
+ double len = cha.getLength().doubleValue(SI.MILLIMETER);
+
+ Amount<Mass> prop = b.getMotor().getGrain().volume(
+ Amount.valueOf(0, SI.MILLIMETER)).times(
+ b.getMotor().getFuel().getIdealDensity().times(
+ b.getMotor().getFuel().getDensityRatio())).to(
+ SI.KILOGRAM);
+ double wt = prop.doubleValue(SI.KILOGRAM);
+
+ out.append(nf.format(dia) + " " + nf.format(len) + " 0-0-0 "
+ + nf.format(wt) + " " + nf.format(wt) + " MF\n");
+
+ GraphSimplifier<Duration, Force> gs = null;
+ try {
+ gs = new GraphSimplifier<Duration, Force>(b, "thrust", b.getData()
+ .keySet().iterator());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return;
+ }
+
+ int cnt = 0;
+ for (Amount<Duration> t : gs.getDomain()) {
+ cnt++;
+ double thrust = gs.value(t).doubleValue(SI.NEWTON);
+ if (cnt < 10 && thrust == 0.0) {
+ continue; // This is a hack to ignore 0 thrust early in burn
+ }
+ out.append(" ");
+ out.append(nf.format(t.doubleValue(SI.SECOND)));
+ out.append(" ");
+ out.append(nf.format(thrust));
+ out.append("\n");
+ }
+ out.append(";\n\n");
+
+ os.write(out.toString().getBytes());
+ }
+}
import java.awt.event.ActionListener;\r
import java.io.File;\r
import java.util.HashMap;\r
+import java.util.Vector;\r
\r
import javax.swing.ButtonGroup;\r
import javax.swing.JFileChooser;\r
import javax.swing.tree.TreePath;\r
import javax.swing.tree.TreeSelectionModel;\r
\r
+import com.billkuker.rocketry.motorsim.Burn;\r
import com.billkuker.rocketry.motorsim.Motor;\r
import com.billkuker.rocketry.motorsim.RocketScience.UnitPreference;\r
+import com.billkuker.rocketry.motorsim.io.ENGExporter;\r
import com.billkuker.rocketry.motorsim.io.MotorIO;\r
\r
public class MotorWorkbench extends JFrame implements TreeSelectionListener {\r
});\r
}\r
});\r
+ add(new JSeparator());\r
+ add(new JMenuItem("Export .ENG"){\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ {\r
+ addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent arg0) {\r
\r
+ final FileDialog fd = new FileDialog(MotorWorkbench.this, "Export .ENG File", FileDialog.SAVE);\r
+ fd.setFile("motorsim.eng");\r
+ fd.setVisible(true);\r
+ if (fd.getFile() != null ) {\r
+ File file = new File(fd.getDirectory() + fd.getFile());\r
+ Vector<Burn> bb = new Vector<Burn>();\r
+ for( MotorEditor me : m2e.values() )\r
+ bb.add(me.burn);\r
+ try{\r
+ ENGExporter.export(bb, file);\r
+ } catch ( Exception e ){\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ });\r
+ }\r
+ });\r
}\r
});\r
add(new JMenu("Settings") {\r