Move net.sf.openrocket.util.Prefs to net.sf.openrocket.gui.util.SwingPreferences.
[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 java.awt.Graphics2D;
7 import java.io.IOException;
8 import java.text.DecimalFormat;
9
10 import net.sf.openrocket.document.OpenRocketDocument;
11 import net.sf.openrocket.document.Simulation;
12 import net.sf.openrocket.gui.figureelements.FigureElement;
13 import net.sf.openrocket.gui.figureelements.RocketInfo;
14 import net.sf.openrocket.gui.scalefigure.RocketPanel;
15 import net.sf.openrocket.gui.util.SwingPreferences;
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         
84         /**
85          * The OR Document.
86          */
87         private OpenRocketDocument rocketDocument;
88         
89         /**
90          * A panel used for rendering of the design diagram.
91          */
92         final RocketPanel panel;
93         
94         /**
95          * The iText document.
96          */
97         protected Document document;
98         
99         /** The displayed strings. */
100         private static final String STAGES = "Stages: ";
101         private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
102         private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
103         private static final String MASS_EMPTY = "Mass (Empty): ";
104         private static final String STABILITY = "Stability: ";
105         private static final String CG = "CG: ";
106         private static final String CP = "CP: ";
107         private static final String MOTOR = "Motor";
108         private static final String AVG_THRUST = "Avg Thrust";
109         private static final String BURN_TIME = "Burn Time";
110         private static final String MAX_THRUST = "Max Thrust";
111         private static final String TOTAL_IMPULSE = "Total Impulse";
112         private static final String THRUST_TO_WT = "Thrust to Wt";
113         private static final String PROPELLANT_WT = "Propellant Wt";
114         private static final String SIZE = "Size";
115         private static final String ALTITUDE = "Altitude";
116         private static final String FLIGHT_TIME = "Flight Time";
117         private static final String TIME_TO_APOGEE = "Time to Apogee";
118         private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
119         private static final String MAX_VELOCITY = "Max Velocity";
120         private static final String LANDING_VELOCITY = "Landing Velocity";
121         private static final String ROCKET_DESIGN = "Rocket Design";
122         private static final double GRAVITY_CONSTANT = 9.80665d;
123         
124         /**
125          * Constructor.
126          *
127          * @param theRocDoc the OR document
128          * @param theIDoc   the iText document
129          */
130         public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc) {
131                 document = theIDoc;
132                 rocketDocument = theRocDoc;
133                 panel = new RocketPanel(rocketDocument);
134         }
135         
136         /**
137          * Main entry point.  Prints the rocket drawing and design data.
138          *
139          * @param writer a direct byte writer
140          */
141         public void writeToDocument(PdfWriter writer) {
142                 if (writer == null) {
143                         return;
144                 }
145                 com.itextpdf.text.Rectangle pageSize = document.getPageSize();
146                 int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
147                 int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
148                 
149                 PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
150                 
151                 Rocket rocket = rocketDocument.getRocket();
152                 final Configuration configuration = rocket.getDefaultConfiguration().clone();
153                 configuration.setAllStages();
154                 PdfContentByte canvas = writer.getDirectContent();
155                 
156                 final PrintFigure figure = new PrintFigure(configuration);
157                 
158                 FigureElement cp = panel.getExtraCP();
159                 FigureElement cg = panel.getExtraCG();
160                 RocketInfo text = panel.getExtraText();
161                 
162                 double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
163                 
164                 canvas.beginText();
165                 try {
166                         canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
167                                                                                                                 BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
168                 } catch (DocumentException e) {
169                         log.error("Could not set font.", e);
170                 } catch (IOException e) {
171                         log.error("Could not create font.", e);
172                 }
173                 int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
174                                 .toPoints(1)));
175                 final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
176                 canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
177                 canvas.moveTextWithLeading(0, -16);
178                 
179                 float initialY = canvas.getYTLM();
180                 
181                 canvas.showText(rocketDocument.getRocket().getName());
182                 
183                 canvas.newlineShowText(STAGES);
184                 canvas.showText("" + rocket.getStageCount());
185                 
186
187                 if (configuration.hasMotors()) {
188                         if (configuration.getStageCount() > 1) {
189                                 canvas.newlineShowText(MASS_WITH_MOTORS);
190                         } else {
191                                 canvas.newlineShowText(MASS_WITH_MOTOR);
192                         }
193                 } else {
194                         canvas.newlineShowText(MASS_EMPTY);
195                 }
196                 canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
197                 
198                 canvas.newlineShowText(STABILITY);
199                 canvas.showText(text.getStability());
200                 
201                 canvas.newlineShowText(CG);
202                 canvas.showText(text.getCg());
203                 
204                 canvas.newlineShowText(CP);
205                 canvas.showText(text.getCp());
206                 canvas.endText();
207                 
208                 try {
209                         //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
210                         Paragraph paragraph = new Paragraph();
211                         float finalY = canvas.getYTLM();
212                         int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
213                         
214                         paragraph.setSpacingAfter(heightOfDiagramAndText);
215                         document.add(paragraph);
216                         
217                         String[] motorIds = rocket.getMotorConfigurationIDs();
218                         
219                         for (int j = 0; j < motorIds.length; j++) {
220                                 String motorId = motorIds[j];
221                                 if (motorId != null) {
222                                         PdfPTable parent = new PdfPTable(2);
223                                         parent.setWidthPercentage(100);
224                                         parent.setHorizontalAlignment(Element.ALIGN_LEFT);
225                                         parent.setSpacingBefore(0);
226                                         parent.setWidths(new int[] { 1, 3 });
227                                         int leading = 0;
228                                         //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
229                                         if (j > 1) {
230                                                 leading = 25;
231                                         }
232                                         addFlightData(rocket, motorId, parent, leading);
233                                         addMotorData(rocket, motorId, parent);
234                                         document.add(parent);
235                                 }
236                         }
237                 } catch (DocumentException e) {
238                         log.error("Could not modify document.", e);
239                 }
240         }
241         
242         
243         /**
244          * Paint a diagram of the rocket into the PDF document.
245          *
246          * @param thePageImageableWidth  the number of points in the width of the page available for drawing
247          * @param thePageImageableHeight the number of points in the height of the page available for drawing
248          * @param theCanvas              the direct byte writer
249          * @param theFigure              the print figure
250          * @param theCp                  the center of pressure figure element
251          * @param theCg                  the center of gravity figure element
252          *
253          * @return the scale of the diagram
254          */
255         private double paintRocketDiagram(final int thePageImageableWidth, final int thePageImageableHeight,
256                                                                                 final PdfContentByte theCanvas, final PrintFigure theFigure,
257                                                                                 final FigureElement theCp, final FigureElement theCg) {
258                 theFigure.clearAbsoluteExtra();
259                 theFigure.clearRelativeExtra();
260                 theFigure.addRelativeExtra(theCp);
261                 theFigure.addRelativeExtra(theCg);
262                 theFigure.updateFigure();
263                 
264                 double scale =
265                                 (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
266                 
267                 theFigure.setScale(scale);
268                 /*
269                  * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
270                  */
271                 theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
272                 theFigure.updateFigure();
273                 
274
275                 final DefaultFontMapper mapper = new DefaultFontMapper();
276                 Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
277                 g2d.translate(20, 120);
278                 
279                 g2d.scale(0.4d, 0.4d);
280                 theFigure.paint(g2d);
281                 g2d.dispose();
282                 return scale;
283         }
284         
285         /**
286          * Add the motor data for a motor configuration to the table.
287          *
288          * @param rocket        the rocket
289          * @param motorId       the motor ID to output
290          * @param parent        the parent to which the motor data will be added
291          */
292         private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) {
293                 
294                 PdfPTable motorTable = new PdfPTable(8);
295                 motorTable.setWidthPercentage(68);
296                 motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
297                 
298                 final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
299                 final int mPad = 10;
300                 motorCell.setPaddingLeft(mPad);
301                 motorTable.addCell(motorCell);
302                 motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
303                 motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
304                 motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
305                 motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
306                 motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
307                 motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
308                 motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
309                 
310                 DecimalFormat ttwFormat = new DecimalFormat("0.00");
311                 
312                 MassCalculator massCalc = new BasicMassCalculator();
313                 
314                 Configuration config = new Configuration(rocket);
315                 config.setMotorConfigurationID(motorId);
316                 
317                 int totalMotorCount = 0;
318                 double totalPropMass = 0;
319                 double totalImpulse = 0;
320                 double totalTTW = 0;
321                 
322                 int stage = 0;
323                 double stageMass = 0;
324                 
325                 boolean topBorder = false;
326                 for (RocketComponent c : rocket) {
327                         
328                         if (c instanceof Stage) {
329                                 config.setToStage(stage);
330                                 stage++;
331                                 stageMass = massCalc.getCG(config, MassCalcType.LAUNCH_MASS).weight;
332                                 // Calculate total thrust-to-weight from only lowest stage motors
333                                 totalTTW = 0;
334                                 topBorder = true;
335                         }
336                         
337                         if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
338                                 MotorMount mount = (MotorMount) c;
339                                 
340                                 if (mount.isMotorMount() && mount.getMotor(motorId) != null) {
341                                         Motor motor = mount.getMotor(motorId);
342                                         int motorCount = c.toAbsolute(Coordinate.NUL).length;
343                                         
344
345                                         int border = Rectangle.NO_BORDER;
346                                         if (topBorder) {
347                                                 border = Rectangle.TOP;
348                                                 topBorder = false;
349                                         }
350                                         
351                                         String name = motor.getDesignation();
352                                         if (motorCount > 1) {
353                                                 name += " (" + Chars.TIMES + motorCount + ")";
354                                         }
355                                         
356                                         final PdfPCell motorVCell = ITextHelper.createCell(name, border);
357                                         motorVCell.setPaddingLeft(mPad);
358                                         motorTable.addCell(motorVCell);
359                                         motorTable.addCell(ITextHelper.createCell(
360                                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getAverageThrustEstimate()), border));
361                                         motorTable.addCell(ITextHelper.createCell(
362                                                         UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(motor.getBurnTimeEstimate()), border));
363                                         motorTable.addCell(ITextHelper.createCell(
364                                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border));
365                                         motorTable.addCell(ITextHelper.createCell(
366                                                         UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border));
367                                         
368                                         double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT);
369                                         motorTable.addCell(ITextHelper.createCell(
370                                                         ttwFormat.format(ttw) + ":1", border));
371                                         
372                                         double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
373                                         motorTable.addCell(ITextHelper.createCell(
374                                                         UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));
375                                         
376                                         final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit();
377                                         motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
378                                                                                                                                 "/" +
379                                                                                                                                 motorUnit.toString(motor.getLength()) + " " +
380                                                                                                                                 motorUnit.toString(), border));
381                                         
382                                         // Sum up total count
383                                         totalMotorCount += motorCount;
384                                         totalPropMass += propMass * motorCount;
385                                         totalImpulse += motor.getTotalImpulseEstimate() * motorCount;
386                                         totalTTW += ttw * motorCount;
387                                 }
388                         }
389                 }
390                 
391                 if (totalMotorCount > 1) {
392                         int border = Rectangle.TOP;
393                         final PdfPCell motorVCell = ITextHelper.createCell("Total:", border);
394                         motorVCell.setPaddingLeft(mPad);
395                         motorTable.addCell(motorVCell);
396                         motorTable.addCell(ITextHelper.createCell("", border));
397                         motorTable.addCell(ITextHelper.createCell("", border));
398                         motorTable.addCell(ITextHelper.createCell("", border));
399                         motorTable.addCell(ITextHelper.createCell(
400                                                 UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(totalImpulse), border));
401                         motorTable.addCell(ITextHelper.createCell(
402                                         ttwFormat.format(totalTTW) + ":1", border));
403                         motorTable.addCell(ITextHelper.createCell(
404                                                 UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border));
405                         motorTable.addCell(ITextHelper.createCell("", border));
406                         
407                 }
408                 
409                 PdfPCell c = new PdfPCell(motorTable);
410                 c.setBorder(PdfPCell.LEFT);
411                 c.setBorderWidthTop(0f);
412                 parent.addCell(c);
413         }
414         
415         
416         /**
417          * Add the motor data for a motor configuration to the table.
418          *
419          * @param theRocket the rocket
420          * @param motorId   a motor configuration id
421          * @param parent    the parent to which the motor data will be added
422          * @param leading   the number of points for the leading
423          */
424         private void addFlightData(final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) {
425                 
426                 // Perform flight simulation
427                 Rocket duplicate = theRocket.copyWithOriginalID();
428                 FlightData flight = null;
429                 try {
430                         Simulation simulation = ((SwingPreferences)Application.getPreferences()).getBackgroundSimulation(duplicate);
431                         simulation.getOptions().setMotorConfigurationID(motorId);
432                         simulation.simulate();
433                         flight = simulation.getSimulatedData();
434                 } catch (SimulationException e1) {
435                         // Ignore
436                 }
437                 
438                 // Output the flight data
439                 if (flight != null) {
440                         try {
441                                 final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
442                                 final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
443                                 final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
444                                 
445                                 PdfPTable labelTable = new PdfPTable(2);
446                                 labelTable.setWidths(new int[] { 3, 2 });
447                                 final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
448                                                         theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD);
449                                 chunk.setLeading(leading);
450                                 chunk.setSpacingAfter(3f);
451                                 
452                                 document.add(chunk);
453                                 
454                                 final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
455                                 cell.setUseBorderPadding(false);
456                                 cell.setBorderWidthTop(0f);
457                                 labelTable.addCell(cell);
458                                 labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2));
459                                 
460                                 labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
461                                 labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2));
462                                 
463                                 labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
464                                 labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
465                                 
466                                 labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
467                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
468                                 
469                                 labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
470                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2));
471                                 
472                                 labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
473                                 labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2));
474                                 
475                                 //Add the table to the parent; have to wrap it in a cell
476                                 PdfPCell c = new PdfPCell(labelTable);
477                                 c.setBorder(PdfPCell.RIGHT);
478                                 c.setBorderWidthTop(0);
479                                 c.setTop(0);
480                                 parent.addCell(c);
481                         } catch (DocumentException e) {
482                                 log.error("Could not add flight data to document.", e);
483                         }
484                 }
485         }
486         
487         /**
488          * Strip [] brackets from a string.
489          *
490          * @param target the original string
491          *
492          * @return target with [] removed
493          */
494         private String stripBrackets(String target) {
495                 return stripLeftBracket(stripRightBracket(target));
496         }
497         
498         /**
499          * Strip [ from a string.
500          *
501          * @param target the original string
502          *
503          * @return target with [ removed
504          */
505         private String stripLeftBracket(String target) {
506                 return target.replace("[", "");
507         }
508         
509         /**
510          * Strip ] from a string.
511          *
512          * @param target the original string
513          *
514          * @return target with ] removed
515          */
516         private String stripRightBracket(String target) {
517                 return target.replace("]", "");
518         }
519         
520 }