DGP - fix to honor launch conditions in printed simulations
[debian/openrocket] / core / src / net / sf / openrocket / gui / print / DesignReport.java
1 /*
2  * DesignReport.java
3  */
4 package net.sf.openrocket.gui.print;
5
6 import java.awt.Graphics2D;
7 import java.io.IOException;
8 import java.text.DecimalFormat;
9 import java.util.List;
10
11 import net.sf.openrocket.document.OpenRocketDocument;
12 import net.sf.openrocket.document.Simulation;
13 import net.sf.openrocket.gui.figureelements.FigureElement;
14 import net.sf.openrocket.gui.figureelements.RocketInfo;
15 import net.sf.openrocket.gui.scalefigure.RocketPanel;
16 import net.sf.openrocket.logging.LogHelper;
17 import net.sf.openrocket.masscalc.BasicMassCalculator;
18 import net.sf.openrocket.masscalc.MassCalculator;
19 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
20 import net.sf.openrocket.motor.Motor;
21 import net.sf.openrocket.rocketcomponent.Configuration;
22 import net.sf.openrocket.rocketcomponent.MotorMount;
23 import net.sf.openrocket.rocketcomponent.Rocket;
24 import net.sf.openrocket.rocketcomponent.RocketComponent;
25 import net.sf.openrocket.rocketcomponent.Stage;
26 import net.sf.openrocket.simulation.FlightData;
27 import net.sf.openrocket.simulation.exception.SimulationException;
28 import net.sf.openrocket.startup.Application;
29 import net.sf.openrocket.unit.Unit;
30 import net.sf.openrocket.unit.UnitGroup;
31 import net.sf.openrocket.util.Chars;
32 import net.sf.openrocket.util.Coordinate;
33
34 import com.itextpdf.text.Document;
35 import com.itextpdf.text.DocumentException;
36 import com.itextpdf.text.Element;
37 import com.itextpdf.text.Paragraph;
38 import com.itextpdf.text.Rectangle;
39 import com.itextpdf.text.pdf.BaseFont;
40 import com.itextpdf.text.pdf.DefaultFontMapper;
41 import com.itextpdf.text.pdf.PdfContentByte;
42 import com.itextpdf.text.pdf.PdfPCell;
43 import com.itextpdf.text.pdf.PdfPTable;
44 import com.itextpdf.text.pdf.PdfWriter;
45
46 /**
47  * <pre>
48  * #  Title # Section describing the rocket in general without motors
49  * # Section describing the rocket in general without motors
50  * <p/>
51  * design name
52  * empty mass & CG
53  * CP position
54  * CP position at 5 degree AOA (or similar)
55  * number of stages
56  * parachute/streamer sizes
57  * max. diameter (caliber)
58  * velocity at exit of rail/rod
59  * minimum safe velocity reached in x inches/cm
60  * <p/>
61  * # Section for each motor configuration
62  * <p/>
63  * a summary of the motors, e.g. 3xC6-0; B4-6
64  * a list of the motors including the manufacturer, designation (maybe also info like burn time, grams of propellant,
65  * total impulse)
66  * total grams of propellant
67  * total impulse
68  * takeoff weight
69  * CG and CP position, stability margin
70  * predicted flight altitude, max. velocity and max. acceleration
71  * predicted velocity at chute deployment
72  * predicted descent rate
73  * Thrust to Weight Ratio of each stage
74  * <p/>
75  * </pre>
76  */
77 public class DesignReport {
78
79         /**
80          * The logger.
81          */
82         private static final LogHelper log = Application.getLogger();
83     public static final double SCALE_FUDGE_FACTOR = 0.4d;
84
85     /**
86          * The OR Document.
87          */
88         private OpenRocketDocument rocketDocument;
89
90         /**
91          * A panel used for rendering of the design diagram.
92          */
93         final RocketPanel panel;
94
95         /**
96          * The iText document.
97          */
98         protected Document document;
99
100     /**
101      * The figure rotation.
102      */
103     private double rotation = 0d;
104
105         /** The displayed strings. */
106         private static final String STAGES = "Stages: ";
107         private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
108         private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
109         private static final String MASS_EMPTY = "Mass (Empty): ";
110         private static final String STABILITY = "Stability: ";
111         private static final String CG = "CG: ";
112         private static final String CP = "CP: ";
113         private static final String MOTOR = "Motor";
114         private static final String AVG_THRUST = "Avg Thrust";
115         private static final String BURN_TIME = "Burn Time";
116         private static final String MAX_THRUST = "Max Thrust";
117         private static final String TOTAL_IMPULSE = "Total Impulse";
118         private static final String THRUST_TO_WT = "Thrust to Wt";
119         private static final String PROPELLANT_WT = "Propellant Wt";
120         private static final String SIZE = "Size";
121         private static final String ALTITUDE = "Altitude";
122         private static final String FLIGHT_TIME = "Flight Time";
123         private static final String TIME_TO_APOGEE = "Time to Apogee";
124         private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
125         private static final String MAX_VELOCITY = "Max Velocity";
126         private static final String DEPLOYMENT_VELOCITY = "Velocity at Deployment";
127         private static final String LANDING_VELOCITY = "Landing Velocity";
128         private static final String ROCKET_DESIGN = "Rocket Design";
129         private static final double GRAVITY_CONSTANT = 9.80665d;
130
131         /**
132          * Constructor.
133          *
134          * @param theRocDoc the OR document
135          * @param theIDoc   the iText document
136      * @param figureRotation the angle the figure is rotated on the screen; printed report will mimic
137          */
138         public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation) {
139                 document = theIDoc;
140                 rocketDocument = theRocDoc;
141                 panel = new RocketPanel(rocketDocument);
142         rotation = figureRotation;
143         }
144
145         /**
146          * Main entry point.  Prints the rocket drawing and design data.
147          *
148          * @param writer a direct byte writer
149          */
150         public void writeToDocument(PdfWriter writer) {
151                 if (writer == null) {
152                         return;
153                 }
154                 com.itextpdf.text.Rectangle pageSize = document.getPageSize();
155                 int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
156                 int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
157
158                 PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
159
160                 Rocket rocket = rocketDocument.getRocket();
161                 final Configuration configuration = rocket.getDefaultConfiguration().clone();
162                 configuration.setAllStages();
163                 PdfContentByte canvas = writer.getDirectContent();
164
165                 final PrintFigure figure = new PrintFigure(configuration);
166         figure.setRotation(rotation);
167
168                 FigureElement cp = panel.getExtraCP();
169                 FigureElement cg = panel.getExtraCG();
170                 RocketInfo text = panel.getExtraText();
171
172                 double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
173
174                 canvas.beginText();
175                 try {
176                         canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
177                                                                                                                 BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
178                 } catch (DocumentException e) {
179                         log.error("Could not set font.", e);
180                 } catch (IOException e) {
181                         log.error("Could not create font.", e);
182                 }
183                 int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
184                                 .toPoints(1)));
185                 final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
186                 canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
187                 canvas.moveTextWithLeading(0, -16);
188
189                 float initialY = canvas.getYTLM();
190
191                 canvas.showText(rocketDocument.getRocket().getName());
192
193                 canvas.newlineShowText(STAGES);
194                 canvas.showText("" + rocket.getStageCount());
195
196
197                 if (configuration.hasMotors()) {
198                         if (configuration.getStageCount() > 1) {
199                                 canvas.newlineShowText(MASS_WITH_MOTORS);
200                         } else {
201                                 canvas.newlineShowText(MASS_WITH_MOTOR);
202                         }
203                 } else {
204                         canvas.newlineShowText(MASS_EMPTY);
205                 }
206                 canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
207
208                 canvas.newlineShowText(STABILITY);
209                 canvas.showText(text.getStability());
210
211                 canvas.newlineShowText(CG);
212                 canvas.showText(text.getCg());
213
214                 canvas.newlineShowText(CP);
215                 canvas.showText(text.getCp());
216                 canvas.endText();
217
218                 try {
219                         //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
220                         Paragraph paragraph = new Paragraph();
221                         float finalY = canvas.getYTLM();
222                         int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
223
224                         paragraph.setSpacingAfter(heightOfDiagramAndText);
225                         document.add(paragraph);
226
227                         String[] motorIds = rocket.getMotorConfigurationIDs();
228             List<Simulation> simulations = rocketDocument.getSimulations();
229
230                         for (int j = 0; j < motorIds.length; j++) {
231                                 String motorId = motorIds[j];
232                                 if (motorId != null) {
233                                         PdfPTable parent = new PdfPTable(2);
234                                         parent.setWidthPercentage(100);
235                                         parent.setHorizontalAlignment(Element.ALIGN_LEFT);
236                                         parent.setSpacingBefore(0);
237                                         parent.setWidths(new int[] { 1, 3 });
238                                         int leading = 0;
239                                         //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
240                                         if (j > 1) {
241                                                 leading = 25;
242                                         }
243                     FlightData flight = findSimulation(motorId, simulations);
244                                         addFlightData(flight, rocket, motorId, parent, leading);
245                                         addMotorData(rocket, motorId, parent);
246                                         document.add(parent);
247                                 }
248                         }
249                 } catch (DocumentException e) {
250                         log.error("Could not modify document.", e);
251                 }
252         }
253
254
255         /**
256          * Paint a diagram of the rocket into the PDF document.
257          *
258          * @param thePageImageableWidth  the number of points in the width of the page available for drawing
259          * @param thePageImageableHeight the number of points in the height of the page available for drawing
260          * @param theCanvas              the direct byte writer
261          * @param theFigure              the print figure
262          * @param theCp                  the center of pressure figure element
263          * @param theCg                  the center of gravity figure element
264          *
265          * @return the scale of the diagram
266          */
267         private double paintRocketDiagram(final int thePageImageableWidth, final int thePageImageableHeight,
268                                                                                 final PdfContentByte theCanvas, final PrintFigure theFigure,
269                                                                                 final FigureElement theCp, final FigureElement theCg) {
270                 theFigure.clearAbsoluteExtra();
271                 theFigure.clearRelativeExtra();
272                 theFigure.addRelativeExtra(theCp);
273                 theFigure.addRelativeExtra(theCg);
274                 theFigure.updateFigure();
275
276                 double scale =
277                                 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
278                 theFigure.setScale(scale);
279                 /*
280                  * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
281                  */
282                 theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
283                 theFigure.updateFigure();
284
285                 final DefaultFontMapper mapper = new DefaultFontMapper();
286                 Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
287         final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeightPx()/2;
288         int y = PrintUnit.POINTS_PER_INCH;
289         //If the y dimension is negative, then it will potentially be drawn off the top of the page.  Move the origin
290         //to allow for this.
291         if (theFigure.getDimensions().getY() < 0.0d) {
292             y += (int)halfFigureHeight;
293         }
294         g2d.translate(20, y);
295
296                 g2d.scale(SCALE_FUDGE_FACTOR, SCALE_FUDGE_FACTOR);
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 rocket        the rocket
306          * @param motorId       the motor ID to output
307          * @param parent        the parent to which the motor data will be added
308          */
309         private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) {
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 ttwFormat = new DecimalFormat("0.00");
328
329                 MassCalculator massCalc = new BasicMassCalculator();
330
331                 Configuration config = new Configuration(rocket);
332                 config.setMotorConfigurationID(motorId);
333
334                 int totalMotorCount = 0;
335                 double totalPropMass = 0;
336                 double totalImpulse = 0;
337                 double totalTTW = 0;
338
339                 int stage = 0;
340                 double stageMass = 0;
341
342                 boolean topBorder = false;
343                 for (RocketComponent c : rocket) {
344
345                         if (c instanceof Stage) {
346                                 config.setToStage(stage);
347                                 stage++;
348                                 stageMass = massCalc.getCG(config, MassCalcType.LAUNCH_MASS).weight;
349                                 // Calculate total thrust-to-weight from only lowest stage motors
350                                 totalTTW = 0;
351                                 topBorder = true;
352                         }
353
354                         if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
355                                 MotorMount mount = (MotorMount) c;
356
357                                 if (mount.isMotorMount() && mount.getMotor(motorId) != null) {
358                                         Motor motor = mount.getMotor(motorId);
359                                         int motorCount = c.toAbsolute(Coordinate.NUL).length;
360
361
362                                         int border = Rectangle.NO_BORDER;
363                                         if (topBorder) {
364                                                 border = Rectangle.TOP;
365                                                 topBorder = false;
366                                         }
367
368                                         String name = motor.getDesignation();
369                                         if (motorCount > 1) {
370                                                 name += " (" + Chars.TIMES + motorCount + ")";
371                                         }
372
373                                         final PdfPCell motorVCell = ITextHelper.createCell(name, border);
374                                         motorVCell.setPaddingLeft(mPad);
375                                         motorTable.addCell(motorVCell);
376                                         motorTable.addCell(ITextHelper.createCell(
377                                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getAverageThrustEstimate()), border));
378                                         motorTable.addCell(ITextHelper.createCell(
379                                                         UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(motor.getBurnTimeEstimate()), border));
380                                         motorTable.addCell(ITextHelper.createCell(
381                                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border));
382                                         motorTable.addCell(ITextHelper.createCell(
383                                                         UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border));
384
385                                         double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT);
386                                         motorTable.addCell(ITextHelper.createCell(
387                                                         ttwFormat.format(ttw) + ":1", border));
388
389                                         double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
390                                         motorTable.addCell(ITextHelper.createCell(
391                                                         UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));
392
393                                         final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit();
394                                         motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
395                                                                                                                                 "/" +
396                                                                                                                                 motorUnit.toString(motor.getLength()) + " " +
397                                                                                                                                 motorUnit.toString(), border));
398
399                                         // Sum up total count
400                                         totalMotorCount += motorCount;
401                                         totalPropMass += propMass * motorCount;
402                                         totalImpulse += motor.getTotalImpulseEstimate() * motorCount;
403                                         totalTTW += ttw * motorCount;
404                                 }
405                         }
406                 }
407
408                 if (totalMotorCount > 1) {
409                         int border = Rectangle.TOP;
410                         final PdfPCell motorVCell = ITextHelper.createCell("Total:", border);
411                         motorVCell.setPaddingLeft(mPad);
412                         motorTable.addCell(motorVCell);
413                         motorTable.addCell(ITextHelper.createCell("", border));
414                         motorTable.addCell(ITextHelper.createCell("", border));
415                         motorTable.addCell(ITextHelper.createCell("", border));
416                         motorTable.addCell(ITextHelper.createCell(
417                                                 UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(totalImpulse), border));
418                         motorTable.addCell(ITextHelper.createCell(
419                                         ttwFormat.format(totalTTW) + ":1", border));
420                         motorTable.addCell(ITextHelper.createCell(
421                                                 UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border));
422                         motorTable.addCell(ITextHelper.createCell("", border));
423
424                 }
425
426                 PdfPCell c = new PdfPCell(motorTable);
427                 c.setBorder(PdfPCell.LEFT);
428                 c.setBorderWidthTop(0f);
429                 parent.addCell(c);
430         }
431
432
433         /**
434          * Add the flight data for a simulation configuration to the table.
435          *
436      * @param flight    the flight data for a single simulation
437          * @param theRocket the rocket
438          * @param motorId   a motor configuration id
439          * @param parent    the parent to which the simulation flight data will be added
440          * @param leading   the number of points for the leading
441          */
442         private void addFlightData(final FlightData flight, final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) {
443
444         // Output the flight data
445                 if (flight != null) {
446                         try {
447                                 final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
448                                 final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
449                                 final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
450
451                                 PdfPTable labelTable = new PdfPTable(2);
452                                 labelTable.setWidths(new int[] { 3, 2 });
453                                 final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
454                                                         theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD);
455                                 chunk.setLeading(leading);
456                                 chunk.setSpacingAfter(3f);
457
458                                 document.add(chunk);
459
460                                 final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
461                                 cell.setUseBorderPadding(false);
462                                 cell.setBorderWidthTop(0f);
463                                 labelTable.addCell(cell);
464                                 labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2));
465
466                                 labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
467                                 labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2));
468
469                                 labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
470                                 labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
471
472                                 labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
473                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
474
475                                 labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
476                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2));
477
478                                 labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2,2));
479                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()),2,2));
480
481                                 labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
482                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2));
483
484                                 //Add the table to the parent; have to wrap it in a cell
485                                 PdfPCell c = new PdfPCell(labelTable);
486                                 c.setBorder(PdfPCell.RIGHT);
487                                 c.setBorderWidthTop(0);
488                                 c.setTop(0);
489                                 parent.addCell(c);
490                         } catch (DocumentException e) {
491                                 log.error("Could not add flight data to document.", e);
492                         }
493                 }
494         }
495
496     /**
497      * Locate the simulation based on the motor id.  Copy the simulation and execute it, then return the resulting
498      * flight data.
499      *
500      * @param motorId     the motor id corresponding to the simulation to find
501      * @param simulations the list of simulations currently associated with the rocket
502      *
503      * @return the flight data from the simulation for the specified motor id, or null if not found
504      */
505     private FlightData findSimulation(final String motorId, List<Simulation> simulations) {
506         // Perform flight simulation
507         FlightData flight = null;
508         try {
509             for (int i = 0; i < simulations.size(); i++) {
510                 Simulation simulation =  simulations.get(i);
511                 if (simulation.getOptions().getMotorConfigurationID().equals(motorId)) {
512                     simulation = simulation.copy();
513                     simulation.simulate();
514                     flight = simulation.getSimulatedData();
515                     break;
516                 }
517             }
518         } catch (SimulationException e1) {
519             // Ignore
520         }
521         return flight;
522     }
523
524     /**
525          * Strip [] brackets from a string.
526          *
527          * @param target the original string
528          *
529          * @return target with [] removed
530          */
531         private String stripBrackets(String target) {
532                 return stripLeftBracket(stripRightBracket(target));
533         }
534
535         /**
536          * Strip [ from a string.
537          *
538          * @param target the original string
539          *
540          * @return target with [ removed
541          */
542         private String stripLeftBracket(String target) {
543                 return target.replace("[", "");
544         }
545
546         /**
547          * Strip ] from a string.
548          *
549          * @param target the original string
550          *
551          * @return target with ] removed
552          */
553         private String stripRightBracket(String target) {
554                 return target.replace("]", "");
555         }
556
557 }