8bc1f0fd19dfd85f545cc6c52b49dbac29ec70ba
[debian/openrocket] / src / net / sf / openrocket / gui / print / DesignReport.java
1 /*
2  * DesignReport.java
3  */
4 package net.sf.openrocket.gui.print;
5
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;
22
23 import java.awt.*;
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;
30
31 /**
32  * <pre>
33  * #  Title # Section describing the rocket in general without motors
34  * # Section describing the rocket in general without motors
35  * <p/>
36  * design name
37  * empty mass & CG
38  * CP position
39  * CP position at 5 degree AOA (or similar)
40  * number of stages
41  * parachute/streamer sizes
42  * max. diameter (caliber)
43  * velocity at exit of rail/rod
44  * minimum safe velocity reached in x inches/cm
45  * <p/>
46  * # Section for each motor configuration
47  * <p/>
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,
50  * total impulse)
51  * total grams of propellant
52  * total impulse
53  * takeoff weight
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
59  * <p/>
60  * </pre>
61  */
62 public class DesignReport {
63
64     /**
65      * The logger.
66      */
67     private static final LogHelper log = Application.getLogger();
68
69     /**
70      * The OR Document.
71      */
72     private OpenRocketDocument rocketDocument;
73
74     /**
75      * A panel used for rendering of the design diagram.
76      */
77     final RocketPanel panel;
78
79     /**
80      * The iText document.
81      */
82     protected Document document;
83
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;
108
109     /**
110      * Constructor.
111      *
112      * @param theRocDoc the OR document
113      * @param theIDoc   the iText document
114      */
115     public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
116         document = theIDoc;
117         rocketDocument = theRocDoc;
118         panel = new RocketPanel(rocketDocument);
119     }
120
121     /**
122      * Main entry point.  Prints the rocket drawing and design data.
123      *
124      * @param writer a direct byte writer
125      */
126     public void writeToDocument (PdfWriter writer) {
127         if (writer == null) {
128             return;
129         }
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();
133
134         PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
135
136         Rocket rocket = rocketDocument.getRocket();
137         final Configuration configuration = rocket.getDefaultConfiguration();
138         configuration.setAllStages();
139         PdfContentByte canvas = writer.getDirectContent();
140
141         final PrintFigure figure = new PrintFigure(configuration);
142
143         FigureElement cp = panel.getExtraCP();
144         FigureElement cg = panel.getExtraCG();
145         RocketInfo text = panel.getExtraText();
146
147         double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
148
149         canvas.beginText();
150         try {
151             canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
152                                                       BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
153         }
154         catch (DocumentException e) {
155             log.error("Could not set font.", e);
156         }
157         catch (IOException e) {
158             log.error("Could not create font.", e);
159         }
160         int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
161                 .toPoints(1)));
162         final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
163         canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
164         canvas.moveTextWithLeading(0, -16);
165
166         float initialY = canvas.getYTLM();
167
168         canvas.showText(rocketDocument.getRocket().getName());
169
170         canvas.newlineShowText(STAGES);
171         canvas.showText("" + rocket.getStageCount());
172
173
174         if (configuration.hasMotors()) {
175             if (configuration.getStageCount() > 1) {
176                 canvas.newlineShowText(MASS_WITH_MOTORS);
177             }
178             else {
179                 canvas.newlineShowText(MASS_WITH_MOTOR);
180             }
181         }
182         else {
183             canvas.newlineShowText(MASS_EMPTY);
184         }
185         canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
186
187         canvas.newlineShowText(STABILITY);
188         canvas.showText(text.getStability());
189
190         canvas.newlineShowText(CG);
191         canvas.showText(text.getCg());
192
193         canvas.newlineShowText(CP);
194         canvas.showText(text.getCp());
195         canvas.endText();
196
197         try {
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));
202
203             paragraph.setSpacingAfter(heightOfDiagramAndText);
204             document.add(paragraph);
205
206             String[] mids = rocket.getMotorConfigurationIDs();
207
208             List<Double> stages = getStageWeights(rocket);
209
210             for (int j = 0; j < mids.length; j++) {
211                 String mid = mids[j];
212                 if (mid != null) {
213                     final List<Motor> motorList = getMotorList(rocket, mid);
214
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});
220                     int leading = 0;
221                     //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
222                     if (j > 1) {
223                         leading = 25;
224                     }
225                     addFlightData(rocket, mid, parent, leading);
226                     addMotorData(motorList, parent, stages);
227                     document.add(parent);
228                 }
229             }
230         }
231         catch (DocumentException e) {
232             log.error("Could not modify document.", e);
233         }
234     }
235
236     /**
237      * Get the motor list for all motor mounts.
238      *
239      * @param theRocket the rocket object
240      * @param theMid    the motor id
241      *
242      * @return a list of Motor
243      */
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);
252                 if (motor != null) {
253                     motorList.add(motor);
254                 }
255             }
256         }
257         return motorList;
258     }
259
260     /**
261      * Paint a diagram of the rocket into the PDF document.
262      *
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
269      *
270      * @return the scale of the diagram
271      */
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();
280
281         double scale =
282                 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
283
284         theFigure.setScale(scale);
285         /*
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
287          */
288         theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
289         theFigure.updateFigure();
290
291
292         final DefaultFontMapper mapper = new DefaultFontMapper();
293         Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
294         g2d.translate(20, 120);
295
296         g2d.scale(0.4d, 0.4d);
297         theFigure.paint(g2d);
298         g2d.dispose();
299         return scale;
300     }
301
302     /**
303      * Add the motor data for a motor configuration to the table.
304      *
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
308      */
309     private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
310
311         PdfPTable motorTable = new PdfPTable(8);
312         motorTable.setWidthPercentage(68);
313         motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
314
315         final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
316         final int mPad = 10;
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));
326
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;
332             }
333             Motor motor = motors.get(i);
334             double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000;  //convert to grams
335
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
340                     .UNITS_FORCE
341                     .getDefaultUnit().toString(), border));
342             motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
343                     .UNITS_FLIGHT_TIME
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
349                     .UNITS_IMPULSE
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));
354
355             motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
356                     .getDefaultUnit().toString(), border));
357
358             final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
359                     .getDefaultUnit();
360             motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
361                                                       "/" +
362                                                       motorUnit.toString(motor.getLength()) + " " +
363                                                       motorUnit.toString(), border));
364         }
365         PdfPCell c = new PdfPCell(motorTable);
366         c.setBorder(PdfPCell.LEFT);
367         c.setBorderWidthTop(0f);
368         parent.addCell(c);
369     }
370
371
372     /**
373      * Add the motor data for a motor configuration to the table.
374      *
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
379      */
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);
386
387             flight = PrintSimulationWorker.doit(simulation);
388
389             if (flight != null) {
390                 try {
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();
394
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);
401
402                     document.add(chunk);
403
404                     DecimalFormat df = new DecimalFormat("#,##0.0#");
405
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,
411                                                               2, 2));
412
413                     labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
414                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
415                                                               2));
416
417                     labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
418                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
419                                                               2));
420
421                     labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
422                     labelTable.addCell(ITextHelper.createCell(df.format(
423                             flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
424
425                     labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
426                     labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
427                                                               2, 2));
428
429                     labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
430                     labelTable.addCell(ITextHelper.createCell(df.format(
431                             flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
432
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);
437                     c.setTop(0);
438                     parent.addCell(c);
439                 }
440                 catch (DocumentException e) {
441                     log.error("Could not add flight data to document.", e);
442                 }
443             }
444         }
445     }
446
447     /**
448      * Strip [] brackets from a string.
449      *
450      * @param target the original string
451      *
452      * @return target with [] removed
453      */
454     private String stripBrackets (String target) {
455         return stripLeftBracket(stripRightBracket(target));
456     }
457
458     /**
459      * Strip [ from a string.
460      *
461      * @param target the original string
462      *
463      * @return target with [ removed
464      */
465     private String stripLeftBracket (String target) {
466         return target.replace("[", "");
467     }
468
469     /**
470      * Strip ] from a string.
471      *
472      * @param target the original string
473      *
474      * @return target with ] removed
475      */
476     private String stripRightBracket (String target) {
477         return target.replace("]", "");
478     }
479
480     /**
481      * From a list of Stages get the stage masses and convert to weight.
482      *
483      * @param rocket the rocket
484      *
485      * @return a sorted list of Stage weights (mass * gravity), in Newtons
486      */
487     private List<Double> getStageWeights (Rocket rocket) {
488         List<Double> stages = getStageMasses(rocket);
489
490         for (int i = 0; i < stages.size(); i++) {
491             Double stage = stages.get(i);
492             stages.set(i, stage * GRAVITY_CONSTANT);
493         }
494         return stages;
495     }
496
497     /**
498      * From a list of Stages get the stage masses.
499      *
500      * @param rocket  the rocket
501      *
502      * @return a sorted list of Stage masses
503      */
504     private List<Double> getStageMasses (final Rocket rocket) {
505         Double mass = 0d;
506
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) {
512                 if (mass > 0d) {
513                     stages.add(mass);
514                     mass = 0d;
515                 }
516             }
517             else {
518                 mass += rocketComponent.getMass();
519             }
520         }
521         if (mass > 0d) {
522             stages.add(mass);
523         }
524         return stages;
525     }
526
527     /**
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).
530      *
531      * @param weights the list of stage weights, in Newtons
532      * @param stage   a stage number, 0 being topmost stage
533      *
534      * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
535      */
536     private double getStageWeight (List<Double> weights, int stage) {
537
538         double result = 0d;
539         for (int i = 0; i <= stage; i++) {
540             result += weights.get(i);
541         }
542         return result;
543     }
544 }