4 package net.sf.openrocket.gui.print;
6 import com.itextpdf.text.*;
7 import com.itextpdf.text.Rectangle;
8 import com.itextpdf.text.pdf.*;
9 import net.sf.openrocket.document.OpenRocketDocument;
10 import net.sf.openrocket.document.Simulation;
11 import net.sf.openrocket.gui.figureelements.FigureElement;
12 import net.sf.openrocket.gui.figureelements.RocketInfo;
13 import net.sf.openrocket.gui.scalefigure.RocketPanel;
14 import net.sf.openrocket.logging.LogHelper;
15 import net.sf.openrocket.motor.Motor;
16 import net.sf.openrocket.rocketcomponent.*;
17 import net.sf.openrocket.simulation.FlightData;
18 import net.sf.openrocket.startup.Application;
19 import net.sf.openrocket.unit.Unit;
20 import net.sf.openrocket.unit.UnitGroup;
21 import net.sf.openrocket.util.Prefs;
24 import java.io.IOException;
25 import java.text.DecimalFormat;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.List;
33 * # Title # Section describing the rocket in general without motors
34 * # Section describing the rocket in general without motors
39 * CP position at 5 degree AOA (or similar)
41 * parachute/streamer sizes
42 * max. diameter (caliber)
43 * velocity at exit of rail/rod
44 * minimum safe velocity reached in x inches/cm
46 * # Section for each motor configuration
48 * a summary of the motors, e.g. 3xC6-0; B4-6
49 * a list of the motors including the manufacturer, designation (maybe also info like burn time, grams of propellant,
51 * total grams of propellant
54 * CG and CP position, stability margin
55 * predicted flight altitude, max. velocity and max. acceleration
56 * predicted velocity at chute deployment
57 * predicted descent rate
58 * Thrust to Weight Ratio of each stage
62 public class DesignReport {
67 private static final LogHelper log = Application.getLogger();
72 private OpenRocketDocument rocketDocument;
75 * A panel used for rendering of the design diagram.
77 final RocketPanel panel;
82 protected Document document;
84 /** The displayed strings. */
85 private static final String STAGES = "Stages: ";
86 private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
87 private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
88 private static final String MASS_EMPTY = "Mass (Empty): ";
89 private static final String STABILITY = "Stability: ";
90 private static final String CG = "Cg: ";
91 private static final String CP = "Cp: ";
92 private static final String MOTOR = "Motor";
93 private static final String AVG_THRUST = "Avg Thrust";
94 private static final String BURN_TIME = "Burn Time";
95 private static final String MAX_THRUST = "Max Thrust";
96 private static final String TOTAL_IMPULSE = "Total Impulse";
97 private static final String THRUST_TO_WT = "Thrust to Wt";
98 private static final String PROPELLANT_WT = "Propellant Wt";
99 private static final String SIZE = "Size";
100 private static final String ALTITUDE = "Altitude";
101 private static final String FLIGHT_TIME = "Flight Time";
102 private static final String TIME_TO_APOGEE = "Time to Apogee";
103 private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
104 private static final String MAX_VELOCITY = "Max Velocity";
105 private static final String LANDING_VELOCITY = "Landing Velocity";
106 private static final String ROCKET_DESIGN = "Rocket Design";
107 private static final double GRAVITY_CONSTANT = 9.80665d;
112 * @param theRocDoc the OR document
113 * @param theIDoc the iText document
115 public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
117 rocketDocument = theRocDoc;
118 panel = new RocketPanel(rocketDocument);
122 * Main entry point. Prints the rocket drawing and design data.
124 * @param writer a direct byte writer
126 public void writeToDocument (PdfWriter writer) {
127 if (writer == null) {
130 com.itextpdf.text.Rectangle pageSize = document.getPageSize();
131 int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
132 int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
134 PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
136 Rocket rocket = rocketDocument.getRocket();
137 final Configuration configuration = rocket.getDefaultConfiguration();
138 configuration.setAllStages();
139 PdfContentByte canvas = writer.getDirectContent();
141 final PrintFigure figure = new PrintFigure(configuration);
143 FigureElement cp = panel.getExtraCP();
144 FigureElement cg = panel.getExtraCG();
145 RocketInfo text = panel.getExtraText();
147 double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
151 canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
152 BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
154 catch (DocumentException e) {
155 log.error("Could not set font.", e);
157 catch (IOException e) {
158 log.error("Could not create font.", e);
160 int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
162 final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
163 canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
164 canvas.moveTextWithLeading(0, -16);
166 float initialY = canvas.getYTLM();
168 canvas.showText(rocketDocument.getRocket().getName());
170 canvas.newlineShowText(STAGES);
171 canvas.showText("" + rocket.getStageCount());
174 if (configuration.hasMotors()) {
175 if (configuration.getStageCount() > 1) {
176 canvas.newlineShowText(MASS_WITH_MOTORS);
179 canvas.newlineShowText(MASS_WITH_MOTOR);
183 canvas.newlineShowText(MASS_EMPTY);
185 canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
187 canvas.newlineShowText(STABILITY);
188 canvas.showText(text.getStability());
190 canvas.newlineShowText(CG);
191 canvas.showText(text.getCg());
193 canvas.newlineShowText(CP);
194 canvas.showText(text.getCp());
198 //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
199 Paragraph paragraph = new Paragraph();
200 float finalY = canvas.getYTLM();
201 int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
203 paragraph.setSpacingAfter(heightOfDiagramAndText);
204 document.add(paragraph);
206 String[] mids = rocket.getMotorConfigurationIDs();
208 List<Double> stages = getStageWeights(rocket);
210 for (int j = 0; j < mids.length; j++) {
211 String mid = mids[j];
213 final List<Motor> motorList = getMotorList(rocket, mid);
215 PdfPTable parent = new PdfPTable(2);
216 parent.setWidthPercentage(100);
217 parent.setHorizontalAlignment(Element.ALIGN_LEFT);
218 parent.setSpacingBefore(0);
219 parent.setWidths(new int[]{1, 3});
221 //The first motor config is always null. Skip it and the top-most motor, then set the leading.
225 addFlightData(rocket, mid, parent, leading);
226 addMotorData(motorList, parent, stages);
227 document.add(parent);
231 catch (DocumentException e) {
232 log.error("Could not modify document.", e);
237 * Get the motor list for all motor mounts.
239 * @param theRocket the rocket object
240 * @param theMid the motor id
242 * @return a list of Motor
244 private List<Motor> getMotorList (final Rocket theRocket, final String theMid) {
245 Iterator<RocketComponent> components = theRocket.deepIterator();
246 final List<Motor> motorList = new ArrayList<Motor>();
247 while (components.hasNext()) {
248 RocketComponent rocketComponent = components.next();
249 if (rocketComponent instanceof MotorMount) {
250 MotorMount mm = (MotorMount) rocketComponent;
251 final Motor motor = mm.getMotor(theMid);
253 motorList.add(motor);
261 * Paint a diagram of the rocket into the PDF document.
263 * @param thePageImageableWidth the number of points in the width of the page available for drawing
264 * @param thePageImageableHeight the number of points in the height of the page available for drawing
265 * @param theCanvas the direct byte writer
266 * @param theFigure the print figure
267 * @param theCp the center of pressure figure element
268 * @param theCg the center of gravity figure element
270 * @return the scale of the diagram
272 private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight,
273 final PdfContentByte theCanvas, final PrintFigure theFigure,
274 final FigureElement theCp, final FigureElement theCg) {
275 theFigure.clearAbsoluteExtra();
276 theFigure.clearRelativeExtra();
277 theFigure.addRelativeExtra(theCp);
278 theFigure.addRelativeExtra(theCg);
279 theFigure.updateFigure();
282 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
284 theFigure.setScale(scale);
286 * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
288 theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
289 theFigure.updateFigure();
292 final DefaultFontMapper mapper = new DefaultFontMapper();
293 Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
294 g2d.translate(20, 120);
296 g2d.scale(0.4d, 0.4d);
297 theFigure.paint(g2d);
303 * Add the motor data for a motor configuration to the table.
305 * @param motors a motor configuration's list of motors
306 * @param parent the parent to which the motor data will be added
307 * @param stageWeights the stageWeights of each stage, in order
309 private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
311 PdfPTable motorTable = new PdfPTable(8);
312 motorTable.setWidthPercentage(68);
313 motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
315 final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
317 motorCell.setPaddingLeft(mPad);
318 motorTable.addCell(motorCell);
319 motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
320 motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
321 motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
322 motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
323 motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
324 motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
325 motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
327 DecimalFormat df = new DecimalFormat("#,##0.0#");
328 for (int i = 0; i < motors.size(); i++) {
329 int border = Rectangle.BOTTOM;
330 if (i == motors.size() - 1) {
331 border = Rectangle.NO_BORDER;
333 Motor motor = motors.get(i);
334 double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000; //convert to grams
336 final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
337 motorVCell.setPaddingLeft(mPad);
338 motorTable.addCell(motorVCell);
339 motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
341 .getDefaultUnit().toString(), border));
342 motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
344 .getDefaultUnit().toString(), border));
345 motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
346 .UNITS_FORCE.getDefaultUnit()
347 .toString(), border));
348 motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
350 .getDefaultUnit().toString(), border));
351 double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
352 .getLaunchCG().weight * GRAVITY_CONSTANT));
353 motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
355 motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
356 .getDefaultUnit().toString(), border));
358 final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
360 motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
362 motorUnit.toString(motor.getLength()) + " " +
363 motorUnit.toString(), border));
365 PdfPCell c = new PdfPCell(motorTable);
366 c.setBorder(PdfPCell.LEFT);
367 c.setBorderWidthTop(0f);
373 * Add the motor data for a motor configuration to the table.
375 * @param theRocket the rocket
376 * @param mid a motor configuration id
377 * @param parent the parent to which the motor data will be added
378 * @param leading the number of points for the leading
380 private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
381 FlightData flight = null;
382 if (theRocket.getMotorConfigurationIDs().length > 1) {
383 Rocket duplicate = theRocket.copyWithOriginalID();
384 Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
385 simulation.getConditions().setMotorConfigurationID(mid);
387 flight = PrintSimulationWorker.doit(simulation);
389 if (flight != null) {
391 final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
392 final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
393 final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
395 PdfPTable labelTable = new PdfPTable(2);
396 labelTable.setWidths(new int[]{3, 2});
397 final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
398 theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
399 chunk.setLeading(leading);
400 chunk.setSpacingAfter(3f);
404 DecimalFormat df = new DecimalFormat("#,##0.0#");
406 final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
407 cell.setUseBorderPadding(false);
408 cell.setBorderWidthTop(0f);
409 labelTable.addCell(cell);
410 labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
413 labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
414 labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
417 labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
418 labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
421 labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
422 labelTable.addCell(ITextHelper.createCell(df.format(
423 flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
425 labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
426 labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
429 labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
430 labelTable.addCell(ITextHelper.createCell(df.format(
431 flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
433 //Add the table to the parent; have to wrap it in a cell
434 PdfPCell c = new PdfPCell(labelTable);
435 c.setBorder(PdfPCell.RIGHT);
436 c.setBorderWidthTop(0);
440 catch (DocumentException e) {
441 log.error("Could not add flight data to document.", e);
448 * Strip [] brackets from a string.
450 * @param target the original string
452 * @return target with [] removed
454 private String stripBrackets (String target) {
455 return stripLeftBracket(stripRightBracket(target));
459 * Strip [ from a string.
461 * @param target the original string
463 * @return target with [ removed
465 private String stripLeftBracket (String target) {
466 return target.replace("[", "");
470 * Strip ] from a string.
472 * @param target the original string
474 * @return target with ] removed
476 private String stripRightBracket (String target) {
477 return target.replace("]", "");
481 * From a list of Stages get the stage masses and convert to weight.
483 * @param rocket the rocket
485 * @return a sorted list of Stage weights (mass * gravity), in Newtons
487 private List<Double> getStageWeights (Rocket rocket) {
488 List<Double> stages = getStageMasses(rocket);
490 for (int i = 0; i < stages.size(); i++) {
491 Double stage = stages.get(i);
492 stages.set(i, stage * GRAVITY_CONSTANT);
498 * From a list of Stages get the stage masses.
500 * @param rocket the rocket
502 * @return a sorted list of Stage masses
504 private List<Double> getStageMasses (final Rocket rocket) {
507 List<Double> stages = new ArrayList<Double>();
508 Iterator<RocketComponent> iter = rocket.deepIterator();
509 while (iter.hasNext()) {
510 RocketComponent rocketComponent = iter.next();
511 if (rocketComponent instanceof Stage) {
518 mass += rocketComponent.getMass();
528 * Compute the total stage weight from a list of stage weights. This sums up the weight of the given stage plus all
529 * stages that sit atop it (depend upon it for thrust).
531 * @param weights the list of stage weights, in Newtons
532 * @param stage a stage number, 0 being topmost stage
534 * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
536 private double getStageWeight (List<Double> weights, int stage) {
539 for (int i = 0; i <= stage; i++) {
540 result += weights.get(i);