DGP - fixes to allow printing a fin template across multiple pages
[debian/openrocket] / src / net / sf / openrocket / gui / print / DesignReport.java
1 /*
2  * DrawingsPrintable.java
3  */
4 package net.sf.openrocket.gui.print;
5
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;
33
34 import java.awt.Graphics2D;
35 import java.io.IOException;
36 import java.text.DecimalFormat;
37 import java.util.List;
38
39 /**
40  * <pre>
41  * #  Title # Section describing the rocket in general without motors
42  * # Section describing the rocket in general without motors
43  * <p/>
44  * design name
45  * empty mass & CG
46  * CP position
47  * CP position at 5 degree AOA (or similar)
48  * number of stages
49  * parachute/streamer sizes
50  * max. diameter (caliber)
51  * velocity at exit of rail/rod
52  * minimum safe velocity reached in x inches/cm
53  * <p/>
54  * # Section for each motor configuration
55  * <p/>
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,
58  * total impulse)
59  * total grams of propellant
60  * total impulse
61  * takeoff weight
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
67  * <p/>
68  * </pre>
69  */
70 public class DesignReport extends BaseVisitorStrategy {
71
72     /**
73      * The OR Document.
74      */
75     private OpenRocketDocument rocketDocument;
76
77     /**
78      * A panel used for rendering of the design diagram.
79      */
80     final RocketPanel panel;
81
82     /**
83      * A stage visitor.
84      */
85     private StageVisitorStrategy svs = new StageVisitorStrategy();
86
87     /**
88      * Constructor.
89      *
90      * @param theRocDoc the OR document
91      * @param theIDoc   the iText document
92      */
93     public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
94         super(theIDoc, null);
95         rocketDocument = theRocDoc;
96         panel = new RocketPanel(rocketDocument);
97     }
98
99     /**
100      * Main entry point.  Prints the rocket drawing and design data.
101      *
102      * @param writer a direct byte writer
103      */
104     public void print (PdfWriter writer) {
105         if (writer == null) {
106             return;
107         }
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();
111
112         PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, "Rocket Design");
113
114         Rocket rocket = rocketDocument.getRocket();
115         final Configuration configuration = rocket.getDefaultConfiguration();
116         configuration.setAllStages();
117         PdfContentByte canvas = writer.getDirectContent();
118
119         final PrintFigure figure = new PrintFigure(configuration);
120
121         FigureElement cp = panel.getExtraCP();
122         FigureElement cg = panel.getExtraCG();
123         RocketInfo text = panel.getExtraText();
124
125         double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
126
127         canvas.beginText();
128         try {
129             canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
130                                                       BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
131         }
132         catch (DocumentException e) {
133             e.printStackTrace();
134         }
135         catch (IOException e) {
136             e.printStackTrace();
137         }
138         int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
139                 .toPoints(1)));
140         final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
141         canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
142         canvas.moveTextWithLeading(0, -16);
143
144         float initialY = canvas.getYTLM();
145
146         canvas.showText(rocketDocument.getRocket().getName());
147
148         canvas.newlineShowText("Stages: ");
149         canvas.showText("" + rocket.getStageCount());
150
151
152         if (configuration.hasMotors()) {
153             canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
154         }
155         else {
156             canvas.newlineShowText("Mass (Empty): ");
157         }
158         canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
159
160         canvas.newlineShowText("Stability: ");
161         canvas.showText(text.getStability());
162
163         canvas.newlineShowText("Cg: ");
164         canvas.showText(text.getCg());
165
166         canvas.newlineShowText("Cp: ");
167         canvas.showText(text.getCp());
168         canvas.endText();
169
170         try {
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));
175
176             paragraph.setSpacingAfter(heightOfDiagramAndText);
177             document.add(paragraph);
178
179             String[] mids = rocket.getMotorConfigurationIDs();
180
181             List<Double> stages = getStageWeights(rocket);
182
183
184             for (int j = 0; j < mids.length; j++) {
185                 String mid = mids[j];
186                 if (mid != null) {
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});
194                     int leading = 0;
195                     //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
196                     if (j > 1) {
197                         leading = 25;
198                     }
199                     addFlightData(rocket, mid, parent, leading);
200                     addMotorData(mmvs.getMotors(), parent, stages);
201                     document.add(parent);
202                 }
203             }
204         }
205         catch (DocumentException e) {
206             e.printStackTrace();
207         }
208     }
209
210     /**
211      * Paint a diagram of the rocket into the PDF document.
212      *
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
219      *
220      * @return the scale of the diagram
221      */
222     private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight, final PdfContentByte theCanvas, final PrintFigure theFigure, final FigureElement theCp, final FigureElement theCg) {
223         theFigure.clearAbsoluteExtra();
224         theFigure.clearRelativeExtra();
225         theFigure.addRelativeExtra(theCp);
226         theFigure.addRelativeExtra(theCg);
227         theFigure.updateFigure();
228
229         double scale =
230                 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
231
232         theFigure.setScale(scale);
233         /*
234          * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
235          */
236         theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
237         theFigure.updateFigure();
238
239
240         final DefaultFontMapper mapper = new DefaultFontMapper();
241         Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
242         g2d.translate(20, 120);
243
244         g2d.scale(0.4d, 0.4d);
245         theFigure.paint(g2d);
246         g2d.dispose();
247         return scale;
248     }
249
250     /**
251      * Add the motor data for a motor configuration to the table.
252      *
253      * @param motors       a motor configuration's list of motors
254      * @param parent       the parent to which the motor data will be added
255      * @param stageWeights the stageWeights of each stage, in order
256      */
257     private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
258
259         PdfPTable motorTable = new PdfPTable(8);
260         motorTable.setWidthPercentage(68);
261         motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
262
263         final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
264         final int mPad = 10;
265         motorCell.setPaddingLeft(mPad);
266         motorTable.addCell(motorCell);
267         motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
268         motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
269         motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
270         motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
271         motorTable.addCell(ITextHelper.createCell("Thrust to Wt", PdfPCell.BOTTOM));
272         motorTable.addCell(ITextHelper.createCell("Propellant Wt", PdfPCell.BOTTOM));
273         motorTable.addCell(ITextHelper.createCell("Size", PdfPCell.BOTTOM));
274
275         DecimalFormat df = new DecimalFormat("#,##0.0#");
276         for (int i = 0; i < motors.size(); i++) {
277             int border = Rectangle.BOTTOM;
278             if (i == motors.size() - 1) {
279                 border = Rectangle.NO_BORDER;
280             }
281             Motor motor = motors.get(i);
282             double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000;  //convert to grams
283
284             final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
285             motorVCell.setPaddingLeft(mPad);
286             motorTable.addCell(motorVCell);
287             motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
288                     .UNITS_FORCE
289                     .getDefaultUnit().toString(), border));
290             motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
291                     .UNITS_FLIGHT_TIME
292                     .getDefaultUnit().toString(), border));
293             motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
294                     .UNITS_FORCE.getDefaultUnit()
295                     .toString(), border));
296             motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
297                     .UNITS_IMPULSE
298                     .getDefaultUnit().toString(), border));
299             double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
300                     .getLaunchCG().weight * 9.80665));
301             motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
302
303             motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
304                     .getDefaultUnit().toString(), border));
305
306             final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
307                     .getDefaultUnit();
308             motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
309                                                       "/" +
310                                                       motorUnit.toString(motor.getLength()) + " " +
311                                                       motorUnit.toString(), border));
312         }
313         PdfPCell c = new PdfPCell(motorTable);
314         c.setBorder(PdfPCell.LEFT);
315         c.setBorderWidthTop(0f);
316         parent.addCell(c);
317     }
318
319
320     /**
321      * Add the motor data for a motor configuration to the table.
322      *
323      * @param theRocket the rocket
324      * @param mid       a motor configuration id
325      * @param parent    the parent to which the motor data will be added
326      * @param leading   the number of points for the leading
327      */
328     private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
329         FlightData flight = null;
330         if (theRocket.getMotorConfigurationIDs().length > 1) {
331             Rocket duplicate = theRocket.copy();
332             Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
333             simulation.getConditions().setMotorConfigurationID(mid);
334
335             flight = PrintSimulationWorker.doit(simulation);
336
337             if (flight != null) {
338                 try {
339                     final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
340                     final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
341                     final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
342
343                     PdfPTable labelTable = new PdfPTable(2);
344                     labelTable.setWidths(new int[]{3, 2});
345                     final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
346                             theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
347                     chunk.setLeading(leading);
348                     chunk.setSpacingAfter(3f);
349
350                     document.add(chunk);
351
352                     DecimalFormat df = new DecimalFormat("#,##0.0#");
353
354                     final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
355                     cell.setUseBorderPadding(false);
356                     cell.setBorderWidthTop(0f);
357                     labelTable.addCell(cell);
358                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
359                                                               2, 2));
360
361                     labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
362                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
363                                                               2));
364
365                     labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
366                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
367                                                               2));
368
369                     labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
370                     labelTable.addCell(ITextHelper.createCell(df.format(
371                             flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
372
373                     labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
374                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
375                                                               2, 2));
376
377                     labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
378                     labelTable.addCell(ITextHelper.createCell(df.format(
379                             flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
380
381                     //Add the table to the parent; have to wrap it in a cell
382                     PdfPCell c = new PdfPCell(labelTable);
383                     c.setBorder(PdfPCell.RIGHT);
384                     c.setBorderWidthTop(0);
385                     c.setTop(0);
386                     parent.addCell(c);
387                 }
388                 catch (DocumentException e) {
389                     e.printStackTrace();
390                 }
391             }
392         }
393     }
394
395     /**
396      * Strip [] brackets from a string.
397      *
398      * @param target the original string
399      *
400      * @return target with [] removed
401      */
402     private String stripBrackets (String target) {
403         return stripLeftBracket(stripRightBracket(target));
404     }
405
406     /**
407      * Strip [ from a string.
408      *
409      * @param target the original string
410      *
411      * @return target with [ removed
412      */
413     private String stripLeftBracket (String target) {
414         return target.replace("[", "");
415     }
416
417     /**
418      * Strip ] from a string.
419      *
420      * @param target the original string
421      *
422      * @return target with ] removed
423      */
424     private String stripRightBracket (String target) {
425         return target.replace("]", "");
426     }
427
428     /**
429      * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
430      * weight.
431      *
432      * @param rocket the rocket
433      *
434      * @return a sorted list of Stage weights (mass * gravity), in Newtons
435      */
436     private List<Double> getStageWeights (Rocket rocket) {
437         rocket.accept(new ComponentVisitor(svs));
438         svs.close();
439         List<Double> stages = svs.getStages();
440         for (int i = 0; i < stages.size(); i++) {
441             Double stage = stages.get(i);
442             stages.set(i, stage * 9.80665);
443         }
444         return stages;
445     }
446
447     /**
448      * Compute the total stage weight from a list of stage weights.  This sums up the weight of the given stage plus all
449      * stages that sit atop it (depend upon it for thrust).
450      *
451      * @param weights the list of stage weights, in Newtons
452      * @param stage   a stage number, 0 being topmost stage
453      *
454      * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
455      */
456     private double getStageWeight (List<Double> weights, int stage) {
457
458         double result = 0d;
459         for (int i = 0; i <= stage; i++) {
460             result += weights.get(i);
461         }
462         return result;
463     }
464 }