DGP - fix for MassObject
[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,
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();
230
231         double scale =
232                 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
233
234         theFigure.setScale(scale);
235         /*
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
237          */
238         theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
239         theFigure.updateFigure();
240
241
242         final DefaultFontMapper mapper = new DefaultFontMapper();
243         Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
244         g2d.translate(20, 120);
245
246         g2d.scale(0.4d, 0.4d);
247         theFigure.paint(g2d);
248         g2d.dispose();
249         return scale;
250     }
251
252     /**
253      * Add the motor data for a motor configuration to the table.
254      *
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
258      */
259     private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
260
261         PdfPTable motorTable = new PdfPTable(8);
262         motorTable.setWidthPercentage(68);
263         motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
264
265         final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
266         final int mPad = 10;
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));
276
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;
282             }
283             Motor motor = motors.get(i);
284             double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000;  //convert to grams
285
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
290                     .UNITS_FORCE
291                     .getDefaultUnit().toString(), border));
292             motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
293                     .UNITS_FLIGHT_TIME
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
299                     .UNITS_IMPULSE
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));
304
305             motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
306                     .getDefaultUnit().toString(), border));
307
308             final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
309                     .getDefaultUnit();
310             motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
311                                                       "/" +
312                                                       motorUnit.toString(motor.getLength()) + " " +
313                                                       motorUnit.toString(), border));
314         }
315         PdfPCell c = new PdfPCell(motorTable);
316         c.setBorder(PdfPCell.LEFT);
317         c.setBorderWidthTop(0f);
318         parent.addCell(c);
319     }
320
321
322     /**
323      * Add the motor data for a motor configuration to the table.
324      *
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
329      */
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);
336
337             flight = PrintSimulationWorker.doit(simulation);
338
339             if (flight != null) {
340                 try {
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();
344
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);
351
352                     document.add(chunk);
353
354                     DecimalFormat df = new DecimalFormat("#,##0.0#");
355
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,
361                                                               2, 2));
362
363                     labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
364                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
365                                                               2));
366
367                     labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
368                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
369                                                               2));
370
371                     labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
372                     labelTable.addCell(ITextHelper.createCell(df.format(
373                             flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
374
375                     labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
376                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
377                                                               2, 2));
378
379                     labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
380                     labelTable.addCell(ITextHelper.createCell(df.format(
381                             flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
382
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);
387                     c.setTop(0);
388                     parent.addCell(c);
389                 }
390                 catch (DocumentException e) {
391                     e.printStackTrace();
392                 }
393             }
394         }
395     }
396
397     /**
398      * Strip [] brackets from a string.
399      *
400      * @param target the original string
401      *
402      * @return target with [] removed
403      */
404     private String stripBrackets (String target) {
405         return stripLeftBracket(stripRightBracket(target));
406     }
407
408     /**
409      * Strip [ from a string.
410      *
411      * @param target the original string
412      *
413      * @return target with [ removed
414      */
415     private String stripLeftBracket (String target) {
416         return target.replace("[", "");
417     }
418
419     /**
420      * Strip ] from a string.
421      *
422      * @param target the original string
423      *
424      * @return target with ] removed
425      */
426     private String stripRightBracket (String target) {
427         return target.replace("]", "");
428     }
429
430     /**
431      * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
432      * weight.
433      *
434      * @param rocket the rocket
435      *
436      * @return a sorted list of Stage weights (mass * gravity), in Newtons
437      */
438     private List<Double> getStageWeights (Rocket rocket) {
439         rocket.accept(new ComponentVisitor(svs));
440         svs.close();
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);
445         }
446         return stages;
447     }
448
449     /**
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).
452      *
453      * @param weights the list of stage weights, in Newtons
454      * @param stage   a stage number, 0 being topmost stage
455      *
456      * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
457      */
458     private double getStageWeight (List<Double> weights, int stage) {
459
460         double result = 0d;
461         for (int i = 0; i <= stage; i++) {
462             result += weights.get(i);
463         }
464         return result;
465     }
466 }