DGP - wordsmithing
[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      */
256     private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> weight) {
257
258         PdfPTable motorTable = new PdfPTable(7);
259         motorTable.setWidthPercentage(68);
260         motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
261
262         final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
263         final int mPad = 12;
264         motorCell.setPaddingLeft(mPad);
265         motorTable.addCell(motorCell);
266         motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
267         motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
268         motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
269         motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
270         motorTable.addCell(ITextHelper.createCell("Thrust to Weight", PdfPCell.BOTTOM));
271         motorTable.addCell(ITextHelper.createCell("Dimensions", PdfPCell.BOTTOM));
272
273         DecimalFormat df = new DecimalFormat("#,##0.0#");
274         for (int i = 0; i < motors.size(); i++) {
275             int border = Rectangle.BOTTOM;
276             if (i == motors.size() - 1) {
277                 border = Rectangle.NO_BORDER;
278             }
279             Motor motor = motors.get(i);
280             final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
281             motorVCell.setPaddingLeft(mPad);
282             motorTable.addCell(motorVCell);
283             motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
284                     .UNITS_FORCE
285                     .getDefaultUnit().toString(), border));
286             motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
287                     .UNITS_FLIGHT_TIME
288                     .getDefaultUnit().toString(), border));
289             motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
290                     .UNITS_FORCE.getDefaultUnit()
291                     .toString(), border));
292             motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
293                     .UNITS_IMPULSE
294                     .getDefaultUnit().toString(), border));
295             double ttw = motor.getAverageThrustEstimate() / getStageWeight(weight, i);
296             motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
297
298             final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
299                     .getDefaultUnit();
300             motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
301                                                       "/" +
302                                                       motorUnit.toString(motor.getLength()) + " " +
303                                                       motorUnit.toString(), border));
304         }
305         PdfPCell c = new PdfPCell(motorTable);
306         c.setBorder(PdfPCell.LEFT);
307         c.setBorderWidthTop(0f);
308         parent.addCell(c);
309     }
310
311
312     /**
313      * Add the motor data for a motor configuration to the table.
314      *
315      * @param theRocket the rocket
316      * @param mid       a motor configuration id
317      * @param parent    the parent to which the motor data will be added
318      * @param leading   the number of points for the leading
319      */
320     private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
321         FlightData flight = null;
322         if (theRocket.getMotorConfigurationIDs().length > 1) {
323             Rocket duplicate = theRocket.copy();
324             Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
325             simulation.getConditions().setMotorConfigurationID(mid);
326
327             flight = PrintSimulationWorker.doit(simulation);
328
329             if (flight != null) {
330                 try {
331                     final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
332                     final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
333                     final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
334
335                     PdfPTable labelTable = new PdfPTable(2);
336                     labelTable.setWidths(new int[]{3, 2});
337                     final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
338                             theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
339                     chunk.setLeading(leading);
340                     chunk.setSpacingAfter(3f);
341
342                     document.add(chunk);
343
344                     DecimalFormat df = new DecimalFormat("#,##0.0#");
345
346                     final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
347                     cell.setUseBorderPadding(false);
348                     cell.setBorderWidthTop(0f);
349                     labelTable.addCell(cell);
350                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
351                                                               2, 2));
352
353                     labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
354                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
355                                                               2));
356
357                     labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
358                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
359                                                               2));
360
361                     labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
362                     labelTable.addCell(ITextHelper.createCell(df.format(
363                             flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
364
365                     labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
366                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
367                                                               2, 2));
368
369                     labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
370                     labelTable.addCell(ITextHelper.createCell(df.format(
371                             flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
372
373                     //Add the table to the parent; have to wrap it in a cell
374                     PdfPCell c = new PdfPCell(labelTable);
375                     c.setBorder(PdfPCell.RIGHT);
376                     c.setBorderWidthTop(0);
377                     c.setTop(0);
378                     parent.addCell(c);
379                 }
380                 catch (DocumentException e) {
381                     e.printStackTrace();
382                 }
383             }
384         }
385     }
386
387     /**
388      * Strip [] brackets from a string.
389      *
390      * @param target the original string
391      *
392      * @return target with [] removed
393      */
394     private String stripBrackets (String target) {
395         return stripLeftBracket(stripRightBracket(target));
396     }
397
398     /**
399      * Strip [ from a string.
400      *
401      * @param target the original string
402      *
403      * @return target with [ removed
404      */
405     private String stripLeftBracket (String target) {
406         return target.replace("[", "");
407     }
408
409     /**
410      * Strip ] from a string.
411      *
412      * @param target the original string
413      *
414      * @return target with ] removed
415      */
416     private String stripRightBracket (String target) {
417         return target.replace("]", "");
418     }
419
420     /**
421      * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
422      * weight.
423      *
424      * @param rocket the rocket
425      *
426      * @return a sorted list of Stage weights (mass * gravity), in Newtons
427      */
428     private List<Double> getStageWeights (Rocket rocket) {
429         rocket.accept(new ComponentVisitor(svs));
430         svs.close();
431         List<Double> stages = svs.getStages();
432         //TODO: Add in motor mass
433         for (int i = 0; i < stages.size(); i++) {
434             Double stage = stages.get(i);
435             stages.set(i, stage * 9.80665);
436         }
437         return stages;
438     }
439
440     /**
441      * Compute the total stage weight from a list of stage weights.  This sums up the weight of the given stage plus all
442      * stages that sit atop it (depend upon it for thrust).
443      *
444      * @param weights the list of stage weights, in Newtons
445      * @param stage   a stage number, 0 being topmost stage
446      *
447      * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
448      */
449     private double getStageWeight (List<Double> weights, int stage) {
450
451         double result = 0d;
452         for (int i = 0; i <= stage; i++) {
453             result += weights.get(i);
454         }
455         return result;
456     }
457 }