@@ -60,485 +78,494 @@ import java.util.List; **/ public class DesignReport { + + /** + * The logger. + */ + private static final LogHelper log = Application.getLogger(); + + /** + * The OR Document. + */ + private OpenRocketDocument rocketDocument; + + /** + * A panel used for rendering of the design diagram. + */ + final RocketPanel panel; + + /** + * The iText document. + */ + protected Document document; + + /** The displayed strings. */ + private static final String STAGES = "Stages: "; + private static final String MASS_WITH_MOTORS = "Mass (with motors): "; + private static final String MASS_WITH_MOTOR = "Mass (with motor): "; + private static final String MASS_EMPTY = "Mass (Empty): "; + private static final String STABILITY = "Stability: "; + private static final String CG = "CG: "; + private static final String CP = "CP: "; + private static final String MOTOR = "Motor"; + private static final String AVG_THRUST = "Avg Thrust"; + private static final String BURN_TIME = "Burn Time"; + private static final String MAX_THRUST = "Max Thrust"; + private static final String TOTAL_IMPULSE = "Total Impulse"; + private static final String THRUST_TO_WT = "Thrust to Wt"; + private static final String PROPELLANT_WT = "Propellant Wt"; + private static final String SIZE = "Size"; + private static final String ALTITUDE = "Altitude"; + private static final String FLIGHT_TIME = "Flight Time"; + private static final String TIME_TO_APOGEE = "Time to Apogee"; + private static final String VELOCITY_OFF_PAD = "Velocity off Pad"; + private static final String MAX_VELOCITY = "Max Velocity"; + private static final String LANDING_VELOCITY = "Landing Velocity"; + private static final String ROCKET_DESIGN = "Rocket Design"; + private static final double GRAVITY_CONSTANT = 9.80665d; + + /** + * Constructor. + * + * @param theRocDoc the OR document + * @param theIDoc the iText document + */ + public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc) { + document = theIDoc; + rocketDocument = theRocDoc; + panel = new RocketPanel(rocketDocument); + } + + /** + * Main entry point. Prints the rocket drawing and design data. + * + * @param writer a direct byte writer + */ + public void writeToDocument(PdfWriter writer) { + if (writer == null) { + return; + } + com.itextpdf.text.Rectangle pageSize = document.getPageSize(); + int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2; + int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop(); + + PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN); + + Rocket rocket = rocketDocument.getRocket(); + final Configuration configuration = rocket.getDefaultConfiguration(); + configuration.setAllStages(); + PdfContentByte canvas = writer.getDirectContent(); + + final PrintFigure figure = new PrintFigure(configuration); + + FigureElement cp = panel.getExtraCP(); + FigureElement cg = panel.getExtraCG(); + RocketInfo text = panel.getExtraText(); + + double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg); + + canvas.beginText(); + try { + canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252, + BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE); + } catch (DocumentException e) { + log.error("Could not set font.", e); + } catch (IOException e) { + log.error("Could not create font.", e); + } + int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS + .toPoints(1))); + final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts); + canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight); + canvas.moveTextWithLeading(0, -16); + + float initialY = canvas.getYTLM(); + + canvas.showText(rocketDocument.getRocket().getName()); + + canvas.newlineShowText(STAGES); + canvas.showText("" + rocket.getStageCount()); + - /** - * The logger. - */ - private static final LogHelper log = Application.getLogger(); + if (configuration.hasMotors()) { + if (configuration.getStageCount() > 1) { + canvas.newlineShowText(MASS_WITH_MOTORS); + } else { + canvas.newlineShowText(MASS_WITH_MOTOR); + } + } else { + canvas.newlineShowText(MASS_EMPTY); + } + canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit())); + + canvas.newlineShowText(STABILITY); + canvas.showText(text.getStability()); + + canvas.newlineShowText(CG); + canvas.showText(text.getCg()); + + canvas.newlineShowText(CP); + canvas.showText(text.getCp()); + canvas.endText(); + + try { + //Move the internal pointer of the document below that of what was just written using the direct byte buffer. + Paragraph paragraph = new Paragraph(); + float finalY = canvas.getYTLM(); + int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight)); + + paragraph.setSpacingAfter(heightOfDiagramAndText); + document.add(paragraph); + + String[] motorIds = rocket.getMotorConfigurationIDs(); + + List
+ * A text token is a string portion that should be surrounded by
+ * braces, "{text}".
+ *
+ * @param original the original string.
+ * @param token the text token to replace.
+ * @param replacement the replacement text.
+ * @return the modified string.
+ */
+ public static String replace(String original, String token, String replacement) {
+ return Pattern.compile(token, Pattern.LITERAL).matcher(original).replaceAll(replacement);
+ }
+
+
+ /**
+ * Convert a language code into a Locale.
+ *
+ * @param langcode the language code (
+ * These methods log the necessary information to the debug log.
+*
+ * @author Sampo Niskanen
+ * The class supports sorting by the name.
+ *
+ * @author Sampo Niskanen null ok).
+ * @return the corresponding locale (or null if the input was null)
+ */
+ public static Locale toLocale(String langcode) {
+ if (langcode == null) {
+ return null;
+ }
+
+ Locale l;
+ String[] split = langcode.split("[_-]", 3);
+ if (split.length == 1) {
+ l = new Locale(split[0]);
+ } else if (split.length == 2) {
+ l = new Locale(split[0], split[1]);
+ } else {
+ l = new Locale(split[0], split[1], split[2]);
+ }
+ return l;
+ }
+
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java b/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java
index f1524e040..0142368d8 100644
--- a/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java
+++ b/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java
@@ -24,7 +24,7 @@ import net.sf.openrocket.util.Prefs;
public class StabilityDomain implements SimulationDomain {
/*
- * FIXME: Should this rather inspect stability during flight
+ * TODO: HIGH: Should this rather inspect stability during flight
*/
private final double limit;
diff --git a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java
index 2dadf6f56..0fa9497c7 100644
--- a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java
+++ b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java
@@ -7,70 +7,72 @@ import net.sf.openrocket.util.MathUtil;
public class EllipticalFinSet extends FinSet {
private static final Translator trans = Application.getTranslator();
-
- public static final int POINTS = 21;
-
+
+ private static final int POINTS = 31;
+
+ // Static positioning for the fin points
private static final double[] POINT_X = new double[POINTS];
private static final double[] POINT_Y = new double[POINTS];
static {
- for (int i=0; i < POINTS; i++) {
- double a = Math.PI * (POINTS-1-i)/(POINTS-1);
- POINT_X[i] = (Math.cos(a)+1)/2;
+ for (int i = 0; i < POINTS; i++) {
+ double a = Math.PI * (POINTS - 1 - i) / (POINTS - 1);
+ POINT_X[i] = (Math.cos(a) + 1) / 2;
POINT_Y[i] = Math.sin(a);
}
POINT_X[0] = 0;
POINT_Y[0] = 0;
- POINT_X[POINTS-1] = 1;
- POINT_Y[POINTS-1] = 0;
+ POINT_X[POINTS - 1] = 1;
+ POINT_Y[POINTS - 1] = 0;
}
-
+
private double height = 0.05;
-
+
public EllipticalFinSet() {
this.length = 0.05;
}
-
-
+
+
@Override
public Coordinate[] getFinPoints() {
+ double len = MathUtil.max(length, 0.0001);
Coordinate[] points = new Coordinate[POINTS];
- for (int i=0; i < POINTS; i++) {
- points[i] = new Coordinate(POINT_X[i]*length, POINT_Y[i]*height);
+ for (int i = 0; i < POINTS; i++) {
+ points[i] = new Coordinate(POINT_X[i] * len, POINT_Y[i] * height);
}
return points;
}
-
+
@Override
public double getSpan() {
return height;
}
-
+
@Override
public String getComponentName() {
//// Elliptical fin set
return trans.get("EllipticalFinSet.Ellipticalfinset");
}
-
-
+
+
public double getHeight() {
return height;
}
-
+
public void setHeight(double height) {
if (MathUtil.equals(this.height, height))
return;
this.height = height;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public void setLength(double length) {
if (MathUtil.equals(this.length, length))
return;
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
}
diff --git a/src/net/sf/openrocket/rocketcomponent/FinSet.java b/src/net/sf/openrocket/rocketcomponent/FinSet.java
index e388b4a6f..d628478a7 100644
--- a/src/net/sf/openrocket/rocketcomponent/FinSet.java
+++ b/src/net/sf/openrocket/rocketcomponent/FinSet.java
@@ -15,8 +15,6 @@ import net.sf.openrocket.util.Transformation;
public abstract class FinSet extends ExternalComponent {
private static final Translator trans = Application.getTranslator();
- // FIXME: converting triangular fins to freeform fails
-
/**
* Maximum allowed cant of fins.
*/
diff --git a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java
index 288726b1f..a852b2fa0 100644
--- a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java
+++ b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java
@@ -1,8 +1,12 @@
package net.sf.openrocket.rocketcomponent;
+import java.util.ArrayList;
+import java.util.List;
+
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
/**
* A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket
@@ -13,9 +17,9 @@ import net.sf.openrocket.util.Coordinate;
public class TrapezoidFinSet extends FinSet {
private static final Translator trans = Application.getTranslator();
-
- public static final double MAX_SWEEP_ANGLE=(89*Math.PI/180.0);
-
+
+ public static final double MAX_SWEEP_ANGLE = (89 * Math.PI / 180.0);
+
/*
* sweep tipChord
* | |___________
@@ -32,64 +36,67 @@ public class TrapezoidFinSet extends FinSet {
private double tipChord = 0;
private double height = 0;
private double sweep = 0;
-
-
+
+
public TrapezoidFinSet() {
- this (3, 0.05, 0.05, 0.025, 0.05);
+ this(3, 0.05, 0.05, 0.025, 0.05);
}
-
+
// TODO: HIGH: height=0 -> CP = NaN
public TrapezoidFinSet(int fins, double rootChord, double tipChord, double sweep,
double height) {
super();
-
+
this.setFinCount(fins);
this.length = rootChord;
this.tipChord = tipChord;
this.sweep = sweep;
this.height = height;
}
-
-
+
+
public void setFinShape(double rootChord, double tipChord, double sweep, double height,
double thickness) {
- if (this.length==rootChord && this.tipChord==tipChord && this.sweep==sweep &&
- this.height==height && this.thickness==thickness)
+ if (this.length == rootChord && this.tipChord == tipChord && this.sweep == sweep &&
+ this.height == height && this.thickness == thickness)
return;
- this.length=rootChord;
- this.tipChord=tipChord;
- this.sweep=sweep;
- this.height=height;
- this.thickness=thickness;
+ this.length = rootChord;
+ this.tipChord = tipChord;
+ this.sweep = sweep;
+ this.height = height;
+ this.thickness = thickness;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getRootChord() {
return length;
}
+
public void setRootChord(double r) {
if (length == r)
return;
- length = Math.max(r,0);
+ length = Math.max(r, 0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getTipChord() {
return tipChord;
}
+
public void setTipChord(double r) {
if (tipChord == r)
return;
- tipChord = Math.max(r,0);
+ tipChord = Math.max(r, 0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
/**
* Get the sweep length.
*/
public double getSweep() {
return sweep;
}
+
/**
* Set the sweep length.
*/
@@ -99,7 +106,7 @@ public class TrapezoidFinSet extends FinSet {
sweep = r;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
/**
* Get the sweep angle. This is calculated from the true sweep and height, and is not
* stored separetely.
@@ -107,13 +114,14 @@ public class TrapezoidFinSet extends FinSet {
public double getSweepAngle() {
if (height == 0) {
if (sweep > 0)
- return Math.PI/2;
+ return Math.PI / 2;
if (sweep < 0)
- return -Math.PI/2;
+ return -Math.PI / 2;
return 0;
}
- return Math.atan(sweep/height);
+ return Math.atan(sweep / height);
}
+
/**
* Sets the sweep by the sweep angle. The sweep is calculated and set by this method,
* and the angle itself is not stored.
@@ -128,34 +136,37 @@ public class TrapezoidFinSet extends FinSet {
return;
setSweep(sweep);
}
-
+
public double getHeight() {
return height;
}
+
public void setHeight(double r) {
if (height == r)
return;
- height = Math.max(r,0);
+ height = Math.max(r, 0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
/**
* Returns the geometry of a trapezoidal fin.
*/
@Override
public Coordinate[] getFinPoints() {
- Coordinate[] c = new Coordinate[4];
-
- c[0] = Coordinate.NUL;
- c[1] = new Coordinate(sweep,height);
- c[2] = new Coordinate(sweep+tipChord,height);
- c[3] = new Coordinate(length,0);
-
- return c;
+ Listtrue to write, false to abort.
+ */
+ public static boolean confirmWrite(File file, Component parent) {
+ if (file.exists()) {
+ log.info(1, "File " + file + " exists, confirming overwrite from user");
+ int result = JOptionPane.showConfirmDialog(parent,
+ L10N.replace(trans.get("error.fileExists.desc"), "{filename}", file.getName()),
+ trans.get("error.fileExists.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if (result != JOptionPane.YES_OPTION) {
+ log.user(1, "User decided not to overwrite the file");
+ return false;
+ }
+ log.user(1, "User decided to overwrite the file");
+ }
+ return true;
+ }
+
+
+ /**
+ * Display an error message to the user that writing a file failed.
+ *
+ * @param e the I/O exception that caused the error.
+ * @param parent the parent component for the dialog.
+ */
+ public static void errorWriting(IOException e, Component parent) {
+
+ log.warn(1, "Error writing to file", e);
+ JOptionPane.showMessageDialog(parent,
+ new Object[] {
+ trans.get("error.writing.desc"),
+ e.getLocalizedMessage()
+ }, trans.get("error.writing.title"), JOptionPane.ERROR_MESSAGE);
+
+ }
+
+}
diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java
index 9f786ec76..7639bcdc7 100644
--- a/src/net/sf/openrocket/util/Icons.java
+++ b/src/net/sf/openrocket/util/Icons.java
@@ -18,7 +18,7 @@ import net.sf.openrocket.startup.Application;
public class Icons {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
static {
log.debug("Starting to load icons");
}
@@ -60,6 +60,7 @@ public class Icons {
public static final Icon EDIT_COPY = loadImageIcon("pix/icons/edit-copy.png", "Copy");
public static final Icon EDIT_PASTE = loadImageIcon("pix/icons/edit-paste.png", "Paste");
public static final Icon EDIT_DELETE = loadImageIcon("pix/icons/edit-delete.png", "Delete");
+ public static final Icon EDIT_SCALE = loadImageIcon("pix/icons/edit-scale.png", "Scale");
public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
diff --git a/src/net/sf/openrocket/util/Named.java b/src/net/sf/openrocket/util/Named.java
new file mode 100644
index 000000000..b91ae46a0
--- /dev/null
+++ b/src/net/sf/openrocket/util/Named.java
@@ -0,0 +1,56 @@
+package net.sf.openrocket.util;
+
+import java.text.Collator;
+
+/**
+ * An object holder that provides a custom toString return value.
+ * null
+ */
+ public static boolean equals(Object first, Object second) {
+ if (first == null) {
+ return second == null;
+ } else {
+ return first.equals(second);
+ }
+ }
+
+}
diff --git a/test/net/sf/openrocket/IntegrationTest.java b/test/net/sf/openrocket/IntegrationTest.java
index c864ec96e..c3452c1c8 100644
--- a/test/net/sf/openrocket/IntegrationTest.java
+++ b/test/net/sf/openrocket/IntegrationTest.java
@@ -17,6 +17,7 @@ import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.file.GeneralRocketLoader;
import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.file.motor.GeneralMotorLoader;
+import net.sf.openrocket.l10n.ResourceBundleTranslator;
import net.sf.openrocket.masscalc.BasicMassCalculator;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
@@ -72,6 +73,7 @@ public class IntegrationTest {
db.startLoading();
assertEquals(1, db.getMotorSets().size());
Application.setMotorSetDatabase(db);
+ Application.setBaseTranslator(new ResourceBundleTranslator("l10n.messages"));
}
/**