2 * DrawingsPrintable.java
4 package net.sf.openrocket.gui.print;
6 import com.itextpdf.text.Document;
7 import com.itextpdf.text.DocumentException;
8 import com.itextpdf.text.Element;
9 import com.itextpdf.text.Paragraph;
10 import com.itextpdf.text.Rectangle;
11 import com.itextpdf.text.pdf.BaseFont;
12 import com.itextpdf.text.pdf.DefaultFontMapper;
13 import com.itextpdf.text.pdf.PdfContentByte;
14 import com.itextpdf.text.pdf.PdfPCell;
15 import com.itextpdf.text.pdf.PdfPTable;
16 import com.itextpdf.text.pdf.PdfWriter;
17 import net.sf.openrocket.document.OpenRocketDocument;
18 import net.sf.openrocket.document.Simulation;
19 import net.sf.openrocket.gui.figureelements.FigureElement;
20 import net.sf.openrocket.gui.figureelements.RocketInfo;
21 import net.sf.openrocket.gui.print.visitor.BaseVisitorStrategy;
22 import net.sf.openrocket.gui.print.visitor.MotorMountVisitorStrategy;
23 import net.sf.openrocket.gui.print.visitor.StageVisitorStrategy;
24 import net.sf.openrocket.gui.scalefigure.RocketPanel;
25 import net.sf.openrocket.motor.Motor;
26 import net.sf.openrocket.rocketcomponent.ComponentVisitor;
27 import net.sf.openrocket.rocketcomponent.Configuration;
28 import net.sf.openrocket.rocketcomponent.Rocket;
29 import net.sf.openrocket.simulation.FlightData;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.unit.UnitGroup;
32 import net.sf.openrocket.util.Prefs;
34 import java.awt.Graphics2D;
35 import java.io.IOException;
36 import java.text.DecimalFormat;
37 import java.util.List;
41 * # Title # Section describing the rocket in general without motors
42 * # Section describing the rocket in general without motors
47 * CP position at 5 degree AOA (or similar)
49 * parachute/streamer sizes
50 * max. diameter (caliber)
51 * velocity at exit of rail/rod
52 * minimum safe velocity reached in x inches/cm
54 * # Section for each motor configuration
56 * a summary of the motors, e.g. 3xC6-0; B4-6
57 * a list of the motors including the manufacturer, designation (maybe also info like burn time, grams of propellant,
59 * total grams of propellant
62 * CG and CP position, stability margin
63 * predicted flight altitude, max. velocity and max. acceleration
64 * predicted velocity at chute deployment
65 * predicted descent rate
66 * Thrust to Weight Ratio of each stage
70 public class DesignReport extends BaseVisitorStrategy {
75 private OpenRocketDocument rocketDocument;
78 * A panel used for rendering of the design diagram.
80 final RocketPanel panel;
85 private StageVisitorStrategy svs = new StageVisitorStrategy();
90 * @param theRocDoc the OR document
91 * @param theIDoc the iText document
93 public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
95 rocketDocument = theRocDoc;
96 panel = new RocketPanel(rocketDocument);
100 * Main entry point. Prints the rocket drawing and design data.
102 * @param writer a direct byte writer
104 public void print (PdfWriter writer) {
105 if (writer == null) {
108 com.itextpdf.text.Rectangle pageSize = document.getPageSize();
109 int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
110 int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
112 PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, "Rocket Design");
114 Rocket rocket = rocketDocument.getRocket();
115 final Configuration configuration = rocket.getDefaultConfiguration();
116 configuration.setAllStages();
117 PdfContentByte canvas = writer.getDirectContent();
119 final PrintFigure figure = new PrintFigure(configuration);
121 FigureElement cp = panel.getExtraCP();
122 FigureElement cg = panel.getExtraCG();
123 RocketInfo text = panel.getExtraText();
125 double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
129 canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
130 BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
132 catch (DocumentException e) {
135 catch (IOException e) {
138 int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
140 final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
141 canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
142 canvas.moveTextWithLeading(0, -16);
144 float initialY = canvas.getYTLM();
146 canvas.showText(rocketDocument.getRocket().getName());
148 canvas.newlineShowText("Stages: ");
149 canvas.showText("" + rocket.getStageCount());
152 if (configuration.hasMotors()) {
153 canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
156 canvas.newlineShowText("Mass (Empty): ");
158 canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
160 canvas.newlineShowText("Stability: ");
161 canvas.showText(text.getStability());
163 canvas.newlineShowText("Cg: ");
164 canvas.showText(text.getCg());
166 canvas.newlineShowText("Cp: ");
167 canvas.showText(text.getCp());
171 //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
172 Paragraph paragraph = new Paragraph();
173 float finalY = canvas.getYTLM();
174 int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
176 paragraph.setSpacingAfter(heightOfDiagramAndText);
177 document.add(paragraph);
179 String[] mids = rocket.getMotorConfigurationIDs();
181 List<Double> stages = getStageWeights(rocket);
184 for (int j = 0; j < mids.length; j++) {
185 String mid = mids[j];
187 MotorMountVisitorStrategy mmvs = new MotorMountVisitorStrategy(document, mid);
188 rocket.accept(new ComponentVisitor(mmvs));
189 PdfPTable parent = new PdfPTable(2);
190 parent.setWidthPercentage(100);
191 parent.setHorizontalAlignment(Element.ALIGN_LEFT);
192 parent.setSpacingBefore(0);
193 parent.setWidths(new int[]{1, 3});
195 //The first motor config is always null. Skip it and the top-most motor, then set the leading.
199 addFlightData(rocket, mid, parent, leading);
200 addMotorData(mmvs.getMotors(), parent, stages);
201 document.add(parent);
205 catch (DocumentException e) {
211 * Paint a diagram of the rocket into the PDF document.
213 * @param thePageImageableWidth the number of points in the width of the page available for drawing
214 * @param thePageImageableHeight the number of points in the height of the page available for drawing
215 * @param theCanvas the direct byte writer
216 * @param theFigure the print figure
217 * @param theCp the center of pressure figure element
218 * @param theCg the center of gravity figure element
220 * @return the scale of the diagram
222 private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight,
223 final PdfContentByte theCanvas, final PrintFigure theFigure,
224 final FigureElement theCp, final FigureElement theCg) {
225 theFigure.clearAbsoluteExtra();
226 theFigure.clearRelativeExtra();
227 theFigure.addRelativeExtra(theCp);
228 theFigure.addRelativeExtra(theCg);
229 theFigure.updateFigure();
232 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
234 theFigure.setScale(scale);
236 * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
238 theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
239 theFigure.updateFigure();
242 final DefaultFontMapper mapper = new DefaultFontMapper();
243 Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
244 g2d.translate(20, 120);
246 g2d.scale(0.4d, 0.4d);
247 theFigure.paint(g2d);
253 * Add the motor data for a motor configuration to the table.
255 * @param motors a motor configuration's list of motors
256 * @param parent the parent to which the motor data will be added
257 * @param stageWeights the stageWeights of each stage, in order
259 private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
261 PdfPTable motorTable = new PdfPTable(8);
262 motorTable.setWidthPercentage(68);
263 motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
265 final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
267 motorCell.setPaddingLeft(mPad);
268 motorTable.addCell(motorCell);
269 motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
270 motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
271 motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
272 motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
273 motorTable.addCell(ITextHelper.createCell("Thrust to Wt", PdfPCell.BOTTOM));
274 motorTable.addCell(ITextHelper.createCell("Propellant Wt", PdfPCell.BOTTOM));
275 motorTable.addCell(ITextHelper.createCell("Size", PdfPCell.BOTTOM));
277 DecimalFormat df = new DecimalFormat("#,##0.0#");
278 for (int i = 0; i < motors.size(); i++) {
279 int border = Rectangle.BOTTOM;
280 if (i == motors.size() - 1) {
281 border = Rectangle.NO_BORDER;
283 Motor motor = motors.get(i);
284 double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000; //convert to grams
286 final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
287 motorVCell.setPaddingLeft(mPad);
288 motorTable.addCell(motorVCell);
289 motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
291 .getDefaultUnit().toString(), border));
292 motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
294 .getDefaultUnit().toString(), border));
295 motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
296 .UNITS_FORCE.getDefaultUnit()
297 .toString(), border));
298 motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
300 .getDefaultUnit().toString(), border));
301 double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
302 .getLaunchCG().weight * 9.80665));
303 motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
305 motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
306 .getDefaultUnit().toString(), border));
308 final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
310 motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
312 motorUnit.toString(motor.getLength()) + " " +
313 motorUnit.toString(), border));
315 PdfPCell c = new PdfPCell(motorTable);
316 c.setBorder(PdfPCell.LEFT);
317 c.setBorderWidthTop(0f);
323 * Add the motor data for a motor configuration to the table.
325 * @param theRocket the rocket
326 * @param mid a motor configuration id
327 * @param parent the parent to which the motor data will be added
328 * @param leading the number of points for the leading
330 private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
331 FlightData flight = null;
332 if (theRocket.getMotorConfigurationIDs().length > 1) {
333 Rocket duplicate = theRocket.copy();
334 Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
335 simulation.getConditions().setMotorConfigurationID(mid);
337 flight = PrintSimulationWorker.doit(simulation);
339 if (flight != null) {
341 final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
342 final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
343 final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
345 PdfPTable labelTable = new PdfPTable(2);
346 labelTable.setWidths(new int[]{3, 2});
347 final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
348 theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
349 chunk.setLeading(leading);
350 chunk.setSpacingAfter(3f);
354 DecimalFormat df = new DecimalFormat("#,##0.0#");
356 final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
357 cell.setUseBorderPadding(false);
358 cell.setBorderWidthTop(0f);
359 labelTable.addCell(cell);
360 labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
363 labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
364 labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
367 labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
368 labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
371 labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
372 labelTable.addCell(ITextHelper.createCell(df.format(
373 flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
375 labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
376 labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
379 labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
380 labelTable.addCell(ITextHelper.createCell(df.format(
381 flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
383 //Add the table to the parent; have to wrap it in a cell
384 PdfPCell c = new PdfPCell(labelTable);
385 c.setBorder(PdfPCell.RIGHT);
386 c.setBorderWidthTop(0);
390 catch (DocumentException e) {
398 * Strip [] brackets from a string.
400 * @param target the original string
402 * @return target with [] removed
404 private String stripBrackets (String target) {
405 return stripLeftBracket(stripRightBracket(target));
409 * Strip [ from a string.
411 * @param target the original string
413 * @return target with [ removed
415 private String stripLeftBracket (String target) {
416 return target.replace("[", "");
420 * Strip ] from a string.
422 * @param target the original string
424 * @return target with ] removed
426 private String stripRightBracket (String target) {
427 return target.replace("]", "");
431 * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
434 * @param rocket the rocket
436 * @return a sorted list of Stage weights (mass * gravity), in Newtons
438 private List<Double> getStageWeights (Rocket rocket) {
439 rocket.accept(new ComponentVisitor(svs));
441 List<Double> stages = svs.getStages();
442 for (int i = 0; i < stages.size(); i++) {
443 Double stage = stages.get(i);
444 stages.set(i, stage * 9.80665);
450 * Compute the total stage weight from a list of stage weights. This sums up the weight of the given stage plus all
451 * stages that sit atop it (depend upon it for thrust).
453 * @param weights the list of stage weights, in Newtons
454 * @param stage a stage number, 0 being topmost stage
456 * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
458 private double getStageWeight (List<Double> weights, int stage) {
461 for (int i = 0; i <= stage; i++) {
462 result += weights.get(i);