From 9dabeba1481539ccf3bfbf232a8ac0a9e3b08a81 Mon Sep 17 00:00:00 2001 From: rodinia814 Date: Sat, 19 Nov 2011 03:37:54 +0000 Subject: [PATCH] DGP - initial support for fin marking guides, transition templates, and nose cones git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@205 180e2498-e6e9-4542-8430-84ac67f01cd8 --- ChangeLog | 4 + l10n/messages.properties | 6 + .../print/AbstractPrintableTransition.java | 86 ++++ .../openrocket/gui/print/FinMarkingGuide.java | 456 ++++++++++++++++++ .../sf/openrocket/gui/print/ITextHelper.java | 23 +- .../gui/print/OpenRocketPrintable.java | 38 +- .../openrocket/gui/print/PrintController.java | 42 +- .../openrocket/gui/print/PrintableFinSet.java | 42 +- .../gui/print/PrintableNoseCone.java | 56 +++ .../gui/print/PrintableTransition.java | 205 ++++++++ .../gui/print/components/RocketPrintTree.java | 27 +- .../visitor/FinMarkingGuideStrategy.java | 167 +++++++ ...Strategy.java => FinSetPrintStrategy.java} | 36 +- .../gui/print/visitor/TransitionStrategy.java | 205 ++++++++ .../SymmetricComponentShapes.java | 27 +- .../gui/rocketfigure/TransitionShapes.java | 35 +- 16 files changed, 1353 insertions(+), 102 deletions(-) create mode 100644 src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java create mode 100644 src/net/sf/openrocket/gui/print/FinMarkingGuide.java create mode 100644 src/net/sf/openrocket/gui/print/PrintableNoseCone.java create mode 100644 src/net/sf/openrocket/gui/print/PrintableTransition.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java rename src/net/sf/openrocket/gui/print/visitor/{FinSetVisitorStrategy.java => FinSetPrintStrategy.java} (82%) create mode 100644 src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java diff --git a/ChangeLog b/ChangeLog index 84038e12..b678d506 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2011-11-18 Doug Pedrick + + * Printable Fin Marking Guides, Transitions, and Nose Cones (simple projection only) + 2011-10-11 Sampo Niskanen * [BUG] Translators fetched before initialization diff --git a/l10n/messages.properties b/l10n/messages.properties index f9a55ae8..d59680dc 100644 --- a/l10n/messages.properties +++ b/l10n/messages.properties @@ -593,6 +593,9 @@ FinSetConfig.lbl.Tabposition = Tab position: FinSetConfig.ttip.Tabposition = The position of the fin tab. FinSetConfig.lbl.relativeto = relative to +!FinMarkingGuide +FinMarkingGuide.lbl.Front = Front + ! MotorDatabaseLoadingDialog MotorDbLoadDlg.title = Loading motors MotorDbLoadDlg.Loadingmotors = Loading motors... @@ -1317,6 +1320,9 @@ Icons.Redo = Redo OpenRocketPrintable.Partsdetail = Parts detail OpenRocketPrintable.Fintemplates = Fin templates +OpenRocketPrintable.Transitiontemplates = Transition templates +OpenRocketPrintable.Noseconetemplates = Nose Cone templates +OpenRocketPrintable.Finmarkingguide = Fin marking guide OpenRocketPrintable.DesignReport = Design Report OpenRocketDocument.Redo = Redo diff --git a/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java b/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java new file mode 100644 index 00000000..8e6042de --- /dev/null +++ b/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java @@ -0,0 +1,86 @@ +package net.sf.openrocket.gui.print; + +import net.sf.openrocket.rocketcomponent.Transition; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +public abstract class AbstractPrintableTransition extends JPanel { + /** + * The stroke of the transition arc. + */ + private final static BasicStroke thinStroke = new BasicStroke(1.0f); + + /** + * The X margin. + */ + protected int marginX = (int) PrintUnit.INCHES.toPoints(0.25f); + + /** + * The Y margin. + */ + protected int marginY = (int) PrintUnit.INCHES.toPoints(0.25f); + + /** + * Constructor. Initialize this printable with the component to be printed. + * + * @param isDoubleBuffered a boolean, true for double-buffering + * @param transition the component to be printed + */ + public AbstractPrintableTransition(boolean isDoubleBuffered, Transition transition) { + super(isDoubleBuffered); + setBackground(Color.white); + init(transition); + } + + /** + * Compute the basic values of each arc of the transition/shroud. This is adapted from + * The Properties of + * Model Rocket Body Tube Transitions, by J.R. Brohm + * + * @param component the transition component + */ + protected abstract void init(Transition component); + + /** + * Draw the component onto the graphics context. + * + * @param g2 the graphics context + */ + protected abstract void draw(Graphics2D g2); + + /** + * Returns a generated image of the transition. May then be used wherever AWT images can be used, or converted to + * another image/picture format and used accordingly. + * + * @return an awt image of the fin set + */ + public Image createImage() { + int width = getWidth() + marginX; + int height = getHeight() + marginY; + // Create a buffered image in which to draw + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // Create a graphics contents on the buffered image + Graphics2D g2d = bufferedImage.createGraphics(); + // Draw graphics + g2d.setBackground(Color.white); + g2d.clearRect(0, 0, width, height); + paintComponent(g2d); + // Graphics context no longer needed so dispose it + g2d.dispose(); + return bufferedImage; + } + + @Override + public void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + g2.setColor(Color.BLACK); + g2.setStroke(thinStroke); + + draw(g2); + } +} diff --git a/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/src/net/sf/openrocket/gui/print/FinMarkingGuide.java new file mode 100644 index 00000000..e9a25770 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/FinMarkingGuide.java @@ -0,0 +1,456 @@ +package net.sf.openrocket.gui.print; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.ExternalComponent; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * This is the core Swing representation of a fin marking guide. It can handle multiple fin sets on the same or + * different body tubes. One marking guide will be created for any body tube that has a finset. If a tube has + * multiple finsets, then they are combined onto one marking guide. It also includes launch lug marking line(s) if lugs + * are present. If (and only if) a launch lug exists, then the word 'Front' is affixed to the leading edge of the + * guide to give orientation. + *

+ */ +public class FinMarkingGuide extends JPanel { + + /** + * The stroke of normal lines. + */ + private final static BasicStroke thinStroke = new BasicStroke(1.0f); + + /** + * The size of the arrow in points. + */ + private static final int ARROW_SIZE = 10; + + /** + * The default guide width in inches. + */ + public final static double DEFAULT_GUIDE_WIDTH = 3d; + + /** + * 2 PI radians (represents a circle). + */ + public final static double TWO_PI = 2 * Math.PI; + + /** + * The I18N translator. + */ + private static final Translator trans = Application.getTranslator(); + + /** + * The margin. + */ + private static final int MARGIN = (int) PrintUnit.INCHES.toPoints(0.25f); + + /** + * The height (circumference) of the biggest body tube with a finset. + */ + private int maxHeight = 0; + + /** + * A map of body tubes, to a list of components that contains finsets and launch lugs. + */ + private Map> markingGuideItems; + + /** + * Constructor. + * + * @param rocket the rocket instance + */ + public FinMarkingGuide(Rocket rocket) { + super(false); + setBackground(Color.white); + markingGuideItems = init(rocket); + //Max of 2 drawing guides horizontally per page. + setSize((int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH) * 2 + 3 * MARGIN, maxHeight); + } + + /** + * Initialize the marking guide class by iterating over a rocket and finding all finsets. + * + * @param component the root rocket component - this is iterated to find all finset and launch lugs + * @return a map of body tubes to lists of finsets and launch lugs. + */ + private Map> init(Rocket component) { + Iterator iter = component.iterator(false); + Map> results = new LinkedHashMap>(); + BodyTube current = null; + int totalHeight = 0; + int iterationHeight = 0; + int count = 0; + + while (iter.hasNext()) { + RocketComponent next = iter.next(); + if (next instanceof BodyTube) { + current = (BodyTube) next; + } else if (next instanceof FinSet || next instanceof LaunchLug) { + java.util.List list = results.get(current); + if (list == null && current != null) { + list = new ArrayList(); + results.put(current, list); + + double radius = current.getOuterRadius(); + int circumferenceInPoints = (int) PrintUnit.METERS.toPoints(radius * TWO_PI); + + // Find the biggest body tube circumference. + if (iterationHeight < (circumferenceInPoints + MARGIN)) { + iterationHeight = circumferenceInPoints + MARGIN; + } + //At most, two marking guides horizontally. After that, move down and back to the left margin. + count++; + if (count % 2 == 0) { + totalHeight += iterationHeight; + iterationHeight = 0; + } + } + if (list != null) { + list.add((ExternalComponent) next); + } + } + } + maxHeight = totalHeight + iterationHeight; + return results; + } + + /** + * Returns a generated image of the fin marking guide. May then be used wherever AWT images can be used, or + * converted to another image/picture format and used accordingly. + * + * @return an awt image of the fin marking guide + */ + public Image createImage() { + int width = getWidth() + 25; + int height = getHeight() + 25; + // Create a buffered image in which to draw + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // Create a graphics context on the buffered image + Graphics2D g2d = bufferedImage.createGraphics(); + // Draw graphics + g2d.setBackground(Color.white); + g2d.clearRect(0, 0, width, height); + paintComponent(g2d); + // Graphics context no longer needed so dispose it + g2d.dispose(); + return bufferedImage; + } + + /** + *

+     *   ---------------------- Page Edge --------------------------------------------------------
+     *   |                                        ^
+     *   |                                        |
+     *   |
+     *   |                                        y
+     *   |
+     *   |                                        |
+     *   P                                        v
+     *   a      ---                 +----------------------------+  ------------
+     *   g<------^-- x ------------>+                            +       ^
+     *   e       |                  +                            +       |
+     *           |                  +                            +     baseYOffset
+     *   E       |                  +                            +       v
+     *   d       |                  +<----------Fin------------->+ -------------
+     *   g       |                  +                            +
+     *   e       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +   baseSpacing
+     *   |       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +<----------Fin------------->+  --------------
+     *   |       |                  +                            +
+     *   | circumferenceInPoints    +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +
+     *   |       |                  +                            +    baseSpacing
+     *   |       |                  +<------Launch Lug --------->+           -----
+     *   |       |                  +                            +                 \
+     *   |       |                  +                            +                 + yLLOffset
+     *   |       |                  +                            +                 /
+     *   |       |                  +<----------Fin------------->+  --------------
+     *   |       |                  +                            +       ^
+     *   |       |                  +                            +       |
+     *   |       |                  +                            +    baseYOffset
+     *   |       v                  +                            +       v
+     *   |      ---                 +----------------------------+  --------------
+     *
+     *                              |<-------- width ----------->|
+     *
+     * yLLOffset is computed from the difference between the base rotation of the fin and the radial direction of the lug.
+     *
+     * Note: There is a current limitation that a tube with multiple launch lugs may not render the lug lines correctly.
+     * 
+ * + * @param g the Graphics context + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + paintFinMarkingGuide(g2); + } + + private void paintFinMarkingGuide(Graphics2D g2) { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + g2.setColor(Color.BLACK); + g2.setStroke(thinStroke); + int x = MARGIN; + int y = MARGIN; + + int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH); + + int column = 0; + for (BodyTube next : markingGuideItems.keySet()) { + double circumferenceInPoints = PrintUnit.METERS.toPoints(next.getOuterRadius() * TWO_PI); + List componentList = markingGuideItems.get(next); + //Don't draw the lug if there are no fins. + if (hasFins(componentList)) { + + drawMarkingGuide(g2, x, y, (int) (circumferenceInPoints), width); + + //Sort so that fins always precede lugs + sort(componentList); + + boolean hasMultipleComponents = componentList.size() > 1; + + double baseSpacing = 0d; + double baseYOrigin = 0; + double finRadial = 0d; + int yFirstFin = y; + int yLastFin = y; + boolean firstFinSet = true; + + //fin1: 42 fin2: 25 + for (ExternalComponent externalComponent : componentList) { + if (externalComponent instanceof FinSet) { + FinSet fins = (FinSet) externalComponent; + int finCount = fins.getFinCount(); + int offset = 0; + baseSpacing = (circumferenceInPoints / finCount); + double baseRotation = fins.getBaseRotation(); + if (!firstFinSet) { + //Adjust the rotation to a positive number. + while (baseRotation < 0) { + baseRotation += TWO_PI / finCount; + } + offset = computeYOffset(y, circumferenceInPoints, baseSpacing, baseYOrigin, finRadial, baseRotation); + } else { + //baseYOrigin is the distance from the top of the marking guide to the first fin of the first fin set. + //This measurement is used to base all subsequent finsets and lugs off of. + baseYOrigin = baseSpacing / 2; + offset = (int) (baseYOrigin) + y; + firstFinSet = false; + } + yFirstFin = y; + yLastFin = y; + finRadial = baseRotation; + + //Draw the fin marking lines. + for (int fin = 0; fin < finCount; fin++) { + if (fin > 0) { + offset += baseSpacing; + yLastFin = offset; + } else { + yFirstFin = offset; + } + drawDoubleArrowLine(g2, x, offset, x + width, offset); + if (hasMultipleComponents) { + g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2); + } + } + } else if (externalComponent instanceof LaunchLug) { + LaunchLug lug = (LaunchLug) externalComponent; + double yLLOffset = (lug.getRadialDirection() - finRadial) / TWO_PI; + //The placement of the lug line must respect the boundary of the outer marking guide. In order + //to do that, place it above or below either the top or bottom fin line, based on the difference + //between their rotational directions. + if (yLLOffset < 0) { + yLLOffset = yLLOffset * circumferenceInPoints + yLastFin; + } else { + yLLOffset = yLLOffset * circumferenceInPoints + yFirstFin; + } + drawDoubleArrowLine(g2, x, (int) yLLOffset, x + width, (int) yLLOffset); + g2.drawString(lug.getName(), x + (width / 3), (int) yLLOffset - 2); + + } + } + //Only if the tube has a lug or multiple finsets does the orientation of the marking guide matter. So print 'Front'. + if (hasMultipleComponents) { + drawFrontIndication(g2, x, y, (int) baseSpacing, (int) circumferenceInPoints, width); + } + + //At most, two marking guides horizontally. After that, move down and back to the left margin. + column++; + if (column % 2 == 0) { + x = MARGIN; + y += circumferenceInPoints + MARGIN; + } else { + x += MARGIN + width; + } + } + } + } + + /** + * Compute the y offset for the next fin line. + * + * @param y the top margin + * @param circumferenceInPoints the circumference (height) of the guide + * @param baseSpacing the circumference / fin count + * @param baseYOrigin the offset from the top of the guide to the first fin of the first fin set drawn + * @param prevBaseRotation the rotation of the previous finset + * @param baseRotation the rotation of the current finset + * @return number of points from the top of the marking guide to the line to be drawn + */ + private int computeYOffset(int y, double circumferenceInPoints, double baseSpacing, double baseYOrigin, double prevBaseRotation, double baseRotation) { + int offset; + double finRadialDifference = (baseRotation - prevBaseRotation) / TWO_PI; + //If the fin line would be off the top of the marking guide, then readjust. + if (baseYOrigin + finRadialDifference * circumferenceInPoints < 0) { + offset = (int) (baseYOrigin + baseSpacing + finRadialDifference * circumferenceInPoints) + y; + } else if (baseYOrigin - finRadialDifference * circumferenceInPoints > 0) { + offset = (int) (finRadialDifference * circumferenceInPoints + baseYOrigin) + y; + } else { + offset = (int) (finRadialDifference * circumferenceInPoints - baseYOrigin) + y; + } + return offset; + } + + /** + * Determines if the list contains a FinSet. + * + * @param list a list of ExternalComponent + * @return true if the list contains at least one FinSet + */ + private boolean hasFins(List list) { + for (ExternalComponent externalComponent : list) { + if (externalComponent instanceof FinSet) { + return true; + } + } + return false; + } + + /** + * Sort a list of ExternalComponent in-place. Forces FinSets to precede Launch Lugs. + * + * @param componentList a list of ExternalComponent + */ + private void sort(List componentList) { + Collections.sort(componentList, new Comparator() { + @Override + public int compare(ExternalComponent o1, ExternalComponent o2) { + if (o1 instanceof FinSet) { + return -1; + } + if (o2 instanceof FinSet) { + return 1; + } + return 0; + } + }); + } + + /** + * Draw the marking guide outline. + * + * @param g2 the graphics context + * @param x the starting x coordinate + * @param y the starting y coordinate + * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube circumference + * @param width the width of the marking guide in print units; somewhat arbitrary + */ + private void drawMarkingGuide(Graphics2D g2, int x, int y, int length, int width) { + Path2D outline = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); + outline.moveTo(x, y); + outline.lineTo(width + x, y); + outline.lineTo(width + x, length + y); + outline.lineTo(x, length + y); + outline.closePath(); + g2.draw(outline); + + //Draw tick marks for alignment, 1/4 of the width in from either edge + int fromEdge = (width) / 4; + final int tickLength = 8; + //Upper left + g2.drawLine(x + fromEdge, y, x + fromEdge, y + tickLength); + //Upper right + g2.drawLine(x + width - fromEdge, y, x + width - fromEdge, y + tickLength); + //Lower left + g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length); + //Lower right + g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length); + } + + /** + * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug exists to + * give proper orientation of the guide (assuming that the lug is asymmetrically positioned with respect to a fin). + * + * @param g2 the graphics context + * @param x the starting x coordinate + * @param y the starting y coordinate + * @param spacing the space between fin lines + * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube circumference + * @param width the width of the marking guide in print units; somewhat arbitrary + */ + private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) { + //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be + //readable, without going to complex string layout prediction logic. + int rotateX = x + width - 16; + int rotateY = y + (int) (spacing * 1.5) + 20; + if (rotateY > y + length + 14) { + rotateY = y + length / 2 - 10; + } + g2.translate(rotateX, rotateY); + g2.rotate(Math.PI / 2); + g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0); + g2.rotate(-Math.PI / 2); + g2.translate(-rotateX, -rotateY); + } + + /** + * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. + * + * @param g2 the graphics context + * @param x1 the starting x coordinate + * @param y1 the starting y coordinate + * @param x2 the ending x coordinate + * @param y2 the ending y coordinate + */ + void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) { + int len = x2 - x1; + + g2.drawLine(x1, y1, x1 + len, y2); + g2.fillPolygon(new int[]{x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len}, + new int[]{y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2}, 4); + + g2.fillPolygon(new int[]{x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1}, + new int[]{y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1}, 4); + } + + +} diff --git a/src/net/sf/openrocket/gui/print/ITextHelper.java b/src/net/sf/openrocket/gui/print/ITextHelper.java index 47276e6a..0b2b6f95 100644 --- a/src/net/sf/openrocket/gui/print/ITextHelper.java +++ b/src/net/sf/openrocket/gui/print/ITextHelper.java @@ -15,7 +15,7 @@ import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.image.BufferedImage; /** @@ -200,43 +200,46 @@ public final class ITextHelper { */ public static void renderImageAcrossPages (Rectangle pageSize, Document doc, PdfWriter writer, java.awt.Image image) throws DocumentException { - // 4/10 of an inch margin - final int margin = (int) (PrintUnit.POINTS_PER_INCH * 0.4f); + final int margin = (int)Math.min(doc.topMargin(), PrintUnit.POINTS_PER_INCH * 0.3f); float wPage = pageSize.getWidth() - 2 * margin; float hPage = pageSize.getHeight() - 2 * margin; float wImage = image.getWidth(null); float hImage = image.getHeight(null); - java.awt.Rectangle crop = new java.awt.Rectangle(0, 0, (int) Math.min(wPage, wImage), (int) Math.min(hPage, hImage)); PdfContentByte content = writer.getDirectContent(); - double adjust; + int ymargin = 0; + while (true) { BufferedImage subImage = ((BufferedImage) image).getSubimage((int) crop.getX(), (int) crop.getY(), (int) crop.getWidth(), (int) crop.getHeight()); - Graphics2D g2 = content.createGraphics(wPage, hPage); - g2.drawImage(subImage, margin - 1, margin - 1, null); + Graphics2D g2 = content.createGraphics(pageSize.getWidth(), pageSize.getHeight()); + g2.drawImage(subImage, margin, ymargin, null); g2.dispose(); - doc.newPage(); + + // After the first page, the y-margin needs to be set. + ymargin = margin; + final int newX = (int) (crop.getWidth() + crop.getX()); if (newX < wImage) { - adjust = Math.min(wImage - newX, wPage); + double adjust = Math.min(wImage - newX, wPage); crop = new java.awt.Rectangle(newX, (int) crop.getY(), (int) adjust, (int) crop.getHeight()); } else { final int newY = (int) (crop.getHeight() + crop.getY()); if (newY < hImage) { - adjust = Math.min(hImage - newY, hPage); + double adjust = Math.min(hImage - newY, hPage); crop = new java.awt.Rectangle(0, newY, (int) Math.min(wPage, wImage), (int) adjust); } else { break; } } + doc.newPage(); } } diff --git a/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java b/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java index d0908a7d..96ea7267 100644 --- a/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java +++ b/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java @@ -6,21 +6,29 @@ package net.sf.openrocket.gui.print; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; +import java.util.ArrayList; +import java.util.List; + /** * This enumeration identifies the various types of information that may be printed. */ public enum OpenRocketPrintable { - //PARTS_LIST("Parts list", true, 0), - //// Parts detail + // Parts detail PARTS_DETAIL("OpenRocketPrintable.Partsdetail", true, 1), - //// + // Finset shape FIN_TEMPLATE("OpenRocketPrintable.Fintemplates", true, 2), - //// Design Report - DESIGN_REPORT("OpenRocketPrintable.DesignReport", false, 3); - + // Fin marking guide. + FIN_MARKING_GUIDE("OpenRocketPrintable.Finmarkingguide", false, 3), + // Transition Templates + TRANSITION_TEMPLATE("OpenRocketPrintable.Transitiontemplates", false, 4), + // Nose Cone Templates + NOSE_CONE_TEMPLATE("OpenRocketPrintable.Noseconetemplates", false, 5), + // Design Report + DESIGN_REPORT("OpenRocketPrintable.DesignReport", false, 6); + private static final Translator trans = Application.getTranslator(); - + /** * The description - will be displayed in the JTree. */ @@ -93,4 +101,20 @@ public enum OpenRocketPrintable { } return null; } + + /** + * Get a list of ordered enum values that do not have stage affinity. + * + * @return a list of OpenRocketPrintable + */ + public static List getUnstaged() { + List unstaged = new ArrayList(); + OpenRocketPrintable[] values = values(); + for (OpenRocketPrintable value : values) { + if (!value.isStageSpecific()) { + unstaged.add(value); + } + } + return unstaged; + } } diff --git a/src/net/sf/openrocket/gui/print/PrintController.java b/src/net/sf/openrocket/gui/print/PrintController.java index 0a71bbbe..69e4df05 100644 --- a/src/net/sf/openrocket/gui/print/PrintController.java +++ b/src/net/sf/openrocket/gui/print/PrintController.java @@ -4,15 +4,6 @@ */ package net.sf.openrocket.gui.print; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Iterator; -import java.util.Set; - -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.gui.print.visitor.FinSetVisitorStrategy; -import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy; - import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.ExceptionConverter; @@ -20,6 +11,16 @@ import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfBoolean; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfWriter; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.print.visitor.FinMarkingGuideStrategy; +import net.sf.openrocket.gui.print.visitor.FinSetPrintStrategy; +import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy; +import net.sf.openrocket.gui.print.visitor.TransitionStrategy; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.Set; /** * This is the main active object for printing. It performs all actions necessary to create and populate the print @@ -33,7 +34,7 @@ public class PrintController { * @param doc the OR document * @param toBePrinted the user chosen items to print * @param outputFile the file being written to - * @param msn the paper size + * @param settings the print settings */ public void print(OpenRocketDocument doc, Iterator toBePrinted, OutputStream outputFile, PrintSettings settings) { @@ -63,7 +64,7 @@ public class PrintController { idoc.newPage(); break; case FIN_TEMPLATE: - final FinSetVisitorStrategy finWriter = new FinSetVisitorStrategy(idoc, + final FinSetPrintStrategy finWriter = new FinSetPrintStrategy(idoc, writer, stages); finWriter.writeToDocument(doc.getRocket()); @@ -76,7 +77,24 @@ public class PrintController { detailVisitor.close(); idoc.newPage(); break; - } + case TRANSITION_TEMPLATE: + final TransitionStrategy tranWriter = new TransitionStrategy(idoc, writer, stages); + tranWriter.writeToDocument(doc.getRocket(), false); + idoc.newPage(); + break; + + case NOSE_CONE_TEMPLATE: + final TransitionStrategy coneWriter = new TransitionStrategy(idoc, writer, stages); + coneWriter.writeToDocument(doc.getRocket(), true); + idoc.newPage(); + break; + + case FIN_MARKING_GUIDE: + final FinMarkingGuideStrategy fmg = new FinMarkingGuideStrategy(idoc, writer); + fmg.writeToDocument(doc.getRocket()); + idoc.newPage(); + break; + } } //Stupid iText throws a really nasty exception if there is no data when close is called. if (writer.getCurrentDocumentSize() <= 140) { diff --git a/src/net/sf/openrocket/gui/print/PrintableFinSet.java b/src/net/sf/openrocket/gui/print/PrintableFinSet.java index 8af42f87..cf07cb13 100644 --- a/src/net/sf/openrocket/gui/print/PrintableFinSet.java +++ b/src/net/sf/openrocket/gui/print/PrintableFinSet.java @@ -6,11 +6,8 @@ package net.sf.openrocket.gui.print; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.util.Coordinate; -import javax.swing.JPanel; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; +import javax.swing.*; +import java.awt.*; import java.awt.geom.GeneralPath; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; @@ -32,11 +29,19 @@ public class PrintableFinSet extends JPanel implements Printable { /** * The X margin. */ - private int marginX = 25; + private final int marginX = (int)(PrintUnit.POINTS_PER_INCH * 0.3f); /** * The Y margin. */ - private int marginY = 25; + private final int marginY = (int)(PrintUnit.POINTS_PER_INCH * 0.3f); + /** + * The minimum X coordinate. + */ + private int minX = 0; + /** + * The minimum Y coordinate. + */ + private int minY = 0; /** * Constructor. @@ -68,8 +73,6 @@ public class PrintableFinSet extends JPanel implements Printable { polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, points.length); polygon.moveTo(0, 0); - int minX = 0; - int minY = 0; int maxX = 0; int maxY = 0; @@ -84,13 +87,7 @@ public class PrintableFinSet extends JPanel implements Printable { } polygon.closePath(); - if (minX < 0) { - marginX += Math.abs(minX); - } - if (minY < 0) { - marginY += Math.abs(minY); - } - setSize(maxX - minX + marginX, maxY - minY + marginY); + setSize(maxX - minX, maxY - minY); } /** @@ -181,7 +178,18 @@ public class PrintableFinSet extends JPanel implements Printable { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; - g2d.translate(marginX, marginY); + int x = 0; + int y = 0; + + // The minimum X/Y can be negative (primarily only Y due to fin tabs; rarely (never) X, but protect both anyway). + if (minX < marginX) { + x = marginX + Math.abs(minX); + } + if (minY < marginY) { + y = marginY + Math.abs(minY); + } + // Reset the origin. + g2d.translate(x, y); g2d.setPaint(TemplateProperties.getFillColor()); g2d.fill(polygon); g2d.setPaint(TemplateProperties.getLineColor()); diff --git a/src/net/sf/openrocket/gui/print/PrintableNoseCone.java b/src/net/sf/openrocket/gui/print/PrintableNoseCone.java new file mode 100644 index 00000000..77ea45b7 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintableNoseCone.java @@ -0,0 +1,56 @@ +package net.sf.openrocket.gui.print; + +import net.sf.openrocket.gui.rocketfigure.TransitionShapes; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.Transformation; + +import java.awt.*; + +public class PrintableNoseCone extends AbstractPrintableTransition { + + /** + * If the component to be drawn is a nose cone, save a reference to it. + */ + private NoseCone target; + + /** + * Construct a printable nose cone. + * + * @param noseCone the component to print + */ + public PrintableNoseCone(Transition noseCone) { + super(false, noseCone); + } + + @Override + protected void init(Transition component) { + + target = (NoseCone) component; + double radius = target.getForeRadius(); + if (radius < target.getAftRadius()) { + radius = target.getAftRadius(); + } + setSize((int) PrintUnit.METERS.toPoints(2 * radius) + marginX, + (int) PrintUnit.METERS.toPoints(target.getLength() + target.getAftShoulderLength()) + marginY); + } + + /** + * Draw a nose cone. + * + * @param g2 the graphics context + */ + protected void draw(Graphics2D g2) { + Shape[] shapes = TransitionShapes.getShapesSide(target, Transformation.rotate_x(0d), PrintUnit.METERS.toPoints(1)); + + if (shapes != null && shapes.length > 0) { + Rectangle r = shapes[0].getBounds(); + g2.translate(marginX + r.getHeight() / 2, marginY); + g2.rotate(Math.PI / 2); + for (Shape shape : shapes) { + g2.draw(shape); + } + g2.rotate(-Math.PI / 2); + } + } +} diff --git a/src/net/sf/openrocket/gui/print/PrintableTransition.java b/src/net/sf/openrocket/gui/print/PrintableTransition.java new file mode 100644 index 00000000..c8e61761 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintableTransition.java @@ -0,0 +1,205 @@ +package net.sf.openrocket.gui.print; + +import net.sf.openrocket.rocketcomponent.Transition; + +import java.awt.*; +import java.awt.geom.Arc2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; + +/** + * This class allows for a Transition to be printable. It does so by decorating an existing transition (which will not be + * modified) and rendering it within a JPanel. The JPanel is not actually visualized on a display, but instead renders + * it to a print device. + *

+ * Note: Currently nose cones are only supported by drawing the 2D projection of the profile. A more useful approach + * may be to draw a myriahedral projection that can be cut out and bent to form the shape. + */ +public class PrintableTransition extends AbstractPrintableTransition { + + /** + * Dashed array value. + */ + private final static float dash1[] = {4.0f}; + /** + * The dashed stroke for glue tab. + */ + private final static BasicStroke dashed = new BasicStroke(1.0f, + BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, + 10.0f, dash1, 0.0f); + + /** + * The layout is an outer arc, an inner arc, and two lines one either endpoints that connect the arcs. + * Most of the math involves transposing geometric cartesian coordinates to the Java AWT coordinate system. + */ + private Path2D gp; + + /** + * The glue tab. + */ + private Path2D glueTab1; + + /** + * The alignment marks. + */ + private Line2D tick1, tick2; + + /** + * The x coordinates for the two ticks drawn at theta degrees. + */ + private int tick3X, tick4X; + + /** + * The angle, in degrees. + */ + private float theta; + + /** + * The x,y coordinates for where the virtual circle center is located. + */ + private int circleCenterX, circleCenterY; + + /** + * Constructor. + * + * @param transition the transition to print + */ + public PrintableTransition(Transition transition) { + super(false, transition); + } + + @Override + protected void init(Transition component) { + + double r1 = component.getAftRadius(); + double r2 = component.getForeRadius(); + + //Regardless of orientation, we have the convention of R1 as the smaller radius. Flip if different. + if (r1 > r2) { + r1 = r2; + r2 = component.getAftRadius(); + } + double len = component.getLength(); + double v = r2 - r1; + double tmp = Math.sqrt(v * v + len * len); + double factor = tmp / v; + + theta = (float) (360d * v / tmp); + + int r1InPoints = (int) PrintUnit.METERS.toPoints(r1 * factor); + int r2InPoints = (int) PrintUnit.METERS.toPoints(r2 * factor); + + int x = marginX; + int tabOffset = 35; + int y = tabOffset + marginY; + + Arc2D.Double outerArc = new Arc2D.Double(); + Arc2D.Double innerArc = new Arc2D.Double(); + + //If the arcs are more than 3/4 of a circle, then assume the height (y) is the same as the radius of the bigger arc. + if (theta >= 270) { + y += r2InPoints; + } + //If the arc is between 1/2 and 3/4 of a circle, then compute the actual height based upon the angle and radius + //of the bigger arc. + else if (theta >= 180) { + double thetaRads = Math.toRadians(theta - 180); + y += (int) ((Math.cos(thetaRads) * r2InPoints) * Math.tan(thetaRads)); + } + + circleCenterY = y; + circleCenterX = r2InPoints + x; + + //Create the larger arc. + outerArc.setArcByCenter(circleCenterX, circleCenterY, r2InPoints, 180, theta, Arc2D.OPEN); + + //Create the smaller arc. + innerArc.setArcByCenter(circleCenterX, circleCenterY, r1InPoints, 180, theta, Arc2D.OPEN); + + //Create the line between the start of the larger arc and the start of the smaller arc. + Path2D.Double line = new Path2D.Double(); + line.setWindingRule(Path2D.WIND_NON_ZERO); + line.moveTo(x, y); + final int width = r2InPoints - r1InPoints; + line.lineTo(width + x, y); + + //Create the line between the endpoint of the larger arc and the endpoint of the smaller arc. + Path2D.Double closingLine = new Path2D.Double(); + closingLine.setWindingRule(Path2D.WIND_NON_ZERO); + Point2D innerArcEndPoint = innerArc.getEndPoint(); + closingLine.moveTo(innerArcEndPoint.getX(), innerArcEndPoint.getY()); + Point2D outerArcEndPoint = outerArc.getEndPoint(); + closingLine.lineTo(outerArcEndPoint.getX(), outerArcEndPoint.getY()); + + //Add all shapes to the polygon path. + gp = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); + gp.append(line, false); + gp.append(outerArc, false); + gp.append(closingLine, false); + gp.append(innerArc, false); + + //Create the glue tab. + glueTab1 = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); + glueTab1.moveTo(x, y); + glueTab1.lineTo(x + tabOffset, y - tabOffset); + glueTab1.lineTo(width + x - tabOffset, y - tabOffset); + glueTab1.lineTo(width + x, y); + + //Create tick marks for alignment, 1/4 of the width in from either edge + int fromEdge = width / 4; + final int tickLength = 8; + //Upper left + tick1 = new Line2D.Float(x + fromEdge, y, x + fromEdge, y + tickLength); + //Upper right + tick2 = new Line2D.Float(x + width - fromEdge, y, x + width - fromEdge, y + tickLength); + + tick3X = r2InPoints - fromEdge; + tick4X = r1InPoints + fromEdge; + + setSize(gp.getBounds().width, gp.getBounds().height + tabOffset); + } + + /** + * Draw alignment marks on an angle. + * + * @param g2 the graphics context + * @param x the center of the circle's x coordinate + * @param y the center of the circle's y + * @param line the line to draw + * @param theta the angle + */ + private void drawAlignmentMarks(Graphics2D g2, int x, int y, Line2D.Float line, float theta) { + g2.translate(x, y); + g2.rotate(Math.toRadians(-theta)); + g2.draw(line); + g2.rotate(Math.toRadians(theta)); + g2.translate(-x, -y); + } + + /** + * Draw a transition. + * + * @param g2 the graphics context + */ + protected void draw(Graphics2D g2) { + //Render it. + g2.draw(gp); + g2.draw(tick1); + g2.draw(tick2); + drawAlignmentMarks(g2, circleCenterX, + circleCenterY, + new Line2D.Float(-tick3X, 0, -tick3X, -8), + theta); + drawAlignmentMarks(g2, circleCenterX, + circleCenterY, + new Line2D.Float(-tick4X, 0, -tick4X, -8), + theta); + + g2.setStroke(dashed); + g2.draw(glueTab1); + } + +} diff --git a/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java b/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java index d3de7bec..5296ea47 100644 --- a/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java +++ b/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java @@ -3,23 +3,22 @@ */ package net.sf.openrocket.gui.print.components; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; +import net.sf.openrocket.gui.print.OpenRocketPrintable; +import net.sf.openrocket.gui.print.PrintableContext; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Stage; -import javax.swing.JTree; +import javax.swing.*; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; - -import net.sf.openrocket.gui.print.OpenRocketPrintable; -import net.sf.openrocket.gui.print.PrintableContext; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.Stage; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; /** * A specialized JTree for displaying various rocket items that can be printed. @@ -76,9 +75,13 @@ public class RocketPrintTree extends JTree { } } } - toAddTo.add(new CheckBoxNode(OpenRocketPrintable.DESIGN_REPORT.getDescription(), + + List unstaged = OpenRocketPrintable.getUnstaged(); + for (int i = 0; i < unstaged.size(); i++) { + toAddTo.add(new CheckBoxNode(unstaged.get(i).getDescription(), INITIAL_CHECKBOX_SELECTED)); - + } + RocketPrintTree tree = new RocketPrintTree(root); tree.addTreeWillExpandListener diff --git a/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java b/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java new file mode 100644 index 00000000..f4e5a526 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java @@ -0,0 +1,167 @@ +package net.sf.openrocket.gui.print.visitor; + +import com.itextpdf.text.Document; +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.Rectangle; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfWriter; +import net.sf.openrocket.gui.print.FinMarkingGuide; +import net.sf.openrocket.gui.print.ITextHelper; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * A strategy for drawing a fin marking guide. As currently implemented, each body tube with a finset will have + * a marking guide. If a tube has multiple fin sets, they are combined onto one marking guide. Launch lugs are supported + * as well. + */ +public class FinMarkingGuideStrategy { + + /** + * The logger. + */ + private static final LogHelper log = Application.getLogger(); + + /** + * The iText document. + */ + protected Document document; + + /** + * The direct iText writer. + */ + protected PdfWriter writer; + + /** + * Constructor. + * + * @param doc The iText document + * @param theWriter The direct iText writer + */ + public FinMarkingGuideStrategy(Document doc, PdfWriter theWriter) { + document = doc; + writer = theWriter; + } + + /** + * Recurse through the given rocket component. + * + * @param root the root component; all children will be visited recursively + */ + public void writeToDocument(final Rocket root) { + render(root); + } + + + /** + * The core behavior of this strategy. + * + * @param rocket the rocket to render all + */ + private void render(final Rocket rocket) { + try { + FinMarkingGuide pfs = new FinMarkingGuide(rocket); + + java.awt.Dimension size = pfs.getSize(); + final Dimension pageSize = getPageSize(); + if (fitsOnOnePage(pageSize, size.getWidth(), size.getHeight())) { + printOnOnePage(pfs); + } else { + BufferedImage image = (BufferedImage) pfs.createImage(); + ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()), + document, writer, image); + } + } catch (DocumentException e) { + log.error("Could not render the fin marking guide.", e); + } + } + + /** + * Determine if the image will fit on the given page. + * + * @param pageSize the page size + * @param wImage the width of the thing to be printed + * @param hImage the height of the thing to be printed + * @return true if the thing to be printed will fit on a single page + */ + private boolean fitsOnOnePage(Dimension pageSize, double wImage, double hImage) { + double wPage = pageSize.getWidth(); + double hPage = pageSize.getHeight(); + + int wRatio = (int) Math.ceil(wImage / wPage); + int hRatio = (int) Math.ceil(hImage / hPage); + + return wRatio <= 1.0d && hRatio <= 1.0d; + } + + /** + * Print the transition. + * + * @param theMarkingGuide the fin marking guide + */ + private void printOnOnePage(final FinMarkingGuide theMarkingGuide) { + Dimension d = getPageSize(); + PdfContentByte cb = writer.getDirectContent(); + Graphics2D g2 = cb.createGraphics(d.width, d.height); + theMarkingGuide.print(g2); + g2.dispose(); + document.newPage(); + } + + /** + * Get the dimensions of the paper page. + * + * @return an internal Dimension + */ + protected Dimension getPageSize() { + return new Dimension(document.getPageSize().getWidth(), + document.getPageSize().getHeight()); + } + + /** + * Convenience class to model a dimension. + */ + class Dimension { + /** + * Width, in points. + */ + public float width; + /** + * Height, in points. + */ + public float height; + + /** + * Constructor. + * + * @param w width + * @param h height + */ + public Dimension(float w, float h) { + width = w; + height = h; + } + + /** + * Get the width. + * + * @return the width + */ + public float getWidth() { + return width; + } + + /** + * Get the height. + * + * @return the height + */ + public float getHeight() { + return height; + } + } +} diff --git a/src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java similarity index 82% rename from src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java rename to src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java index a079bb80..80e15a21 100644 --- a/src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java +++ b/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java @@ -1,5 +1,5 @@ /* - * FinSetVisitorStrategy.java + * FinSetPrintStrategy.java */ package net.sf.openrocket.gui.print.visitor; @@ -23,7 +23,7 @@ import java.util.Set; /** * A strategy for drawing fin templates. */ -public class FinSetVisitorStrategy { +public class FinSetPrintStrategy { /** * The logger. @@ -50,18 +50,18 @@ public class FinSetVisitorStrategy { * * @param doc The iText document * @param theWriter The direct iText writer - * @param theStagesToVisit The stages to be visited by this strategy + * @param theStages The stages to be printed by this strategy */ - public FinSetVisitorStrategy (Document doc, PdfWriter theWriter, Set theStagesToVisit) { + public FinSetPrintStrategy(Document doc, PdfWriter theWriter, Set theStages) { document = doc; writer = theWriter; - stages = theStagesToVisit; + stages = theStages; } /** * Recurse through the given rocket component. * - * @param root the root component; all children will be visited recursively + * @param root the root component; all children will be printed recursively */ public void writeToDocument (final RocketComponent root) { List rc = root.getChildren(); @@ -72,12 +72,12 @@ public class FinSetVisitorStrategy { /** * Recurse through the given rocket component. * - * @param theRc an array of rocket components; all children will be visited recursively + * @param theRc an array of rocket components; all children will be printed recursively */ protected void goDeep (final List theRc) { for (RocketComponent rocketComponent : theRc) { if (rocketComponent instanceof FinSet) { - doVisit((FinSet)rocketComponent); + printFinSet((FinSet) rocketComponent); } else if (rocketComponent.getChildCount() > 0) { goDeep(rocketComponent.getChildren()); @@ -86,14 +86,14 @@ public class FinSetVisitorStrategy { } /** - * The core behavior of this visitor. + * The core behavior of this strategy. * - * @param visitable the object to extract info about; a graphical image of the fin shape is drawn to the document + * @param finSet the object to extract info about; a graphical image of the fin shape is drawn to the document */ - private void doVisit (final FinSet visitable) { - if (shouldVisitStage(visitable.getStageNumber())) { + private void printFinSet(final FinSet finSet) { + if (shouldPrintStage(finSet.getStageNumber())) { try { - PrintableFinSet pfs = new PrintableFinSet(visitable); + PrintableFinSet pfs = new PrintableFinSet(finSet); java.awt.Dimension finSize = pfs.getSize(); final Dimension pageSize = getPageSize(); @@ -103,8 +103,9 @@ public class FinSetVisitorStrategy { else { BufferedImage image = (BufferedImage) pfs.createImage(); ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()), - document, writer, image); + document, writer, image); } + document.newPage(); } catch (DocumentException e) { log.error("Could not render fin.", e); @@ -113,13 +114,13 @@ public class FinSetVisitorStrategy { } /** - * Determine if the visitor strategy's set of stage numbers (to print) contains the specified stage. + * Determine if the strategy's set of stage numbers (to print) contains the specified stage. * * @param stageNumber a stage number * - * @return true if the visitor strategy contains the stage number provided + * @return true if the strategy contains the stage number provided */ - public boolean shouldVisitStage (int stageNumber) { + public boolean shouldPrintStage(int stageNumber) { if (stages == null || stages.isEmpty()) { return false; } @@ -163,7 +164,6 @@ public class FinSetVisitorStrategy { Graphics2D g2 = cb.createGraphics(d.width, d.height); thePfs.print(g2); g2.dispose(); - document.newPage(); } /** diff --git a/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java b/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java new file mode 100644 index 00000000..1988ab8a --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java @@ -0,0 +1,205 @@ +package net.sf.openrocket.gui.print.visitor; + +import com.itextpdf.text.Document; +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.Rectangle; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfWriter; +import net.sf.openrocket.gui.print.AbstractPrintableTransition; +import net.sf.openrocket.gui.print.ITextHelper; +import net.sf.openrocket.gui.print.PrintableNoseCone; +import net.sf.openrocket.gui.print.PrintableTransition; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.startup.Application; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.Set; + +/** + * A strategy for drawing transition/shroud/nose cone templates. + */ +public class TransitionStrategy { + + /** + * The logger. + */ + private static final LogHelper log = Application.getLogger(); + + /** + * The iText document. + */ + protected Document document; + + /** + * The direct iText writer. + */ + protected PdfWriter writer; + + /** + * The stages selected. + */ + protected Set stages; + + /** + * Constructor. + * + * @param doc The iText document + * @param theWriter The direct iText writer + * @param theStagesToVisit The stages to be visited by this strategy + */ + public TransitionStrategy(Document doc, PdfWriter theWriter, Set theStagesToVisit) { + document = doc; + writer = theWriter; + stages = theStagesToVisit; + } + + /** + * Recurse through the given rocket component. + * + * @param root the root component; all children will be visited recursively + * @param noseCones nose cones are a special form of a transition; if true, then print nose cones + */ + public void writeToDocument(final RocketComponent root, boolean noseCones) { + List rc = root.getChildren(); + goDeep(rc, noseCones); + } + + + /** + * Recurse through the given rocket component. + * + * @param theRc an array of rocket components; all children will be visited recursively + * @param noseCones nose cones are a special form of a transition; if true, then print nose cones + */ + protected void goDeep(final List theRc, boolean noseCones) { + for (RocketComponent rocketComponent : theRc) { + if (rocketComponent instanceof NoseCone) { + if (noseCones) { + render((Transition) rocketComponent); + } + } else if (rocketComponent instanceof Transition && !noseCones) { + render((Transition) rocketComponent); + } else if (rocketComponent.getChildCount() > 0) { + goDeep(rocketComponent.getChildren(), noseCones); + } + } + } + + /** + * The core behavior of this visitor. + * + * @param component the object to extract info about; a graphical image of the transition shape is drawn to the document + */ + private void render(final Transition component) { + try { + AbstractPrintableTransition pfs; + if (component instanceof NoseCone) { + pfs = new PrintableNoseCone(component); + } else { + pfs = new PrintableTransition(component); + } + + java.awt.Dimension size = pfs.getSize(); + final Dimension pageSize = getPageSize(); + if (fitsOnOnePage(pageSize, size.getWidth(), size.getHeight())) { + printOnOnePage(pfs); + } else { + BufferedImage image = (BufferedImage) pfs.createImage(); + ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()), + document, writer, image); + } + } catch (DocumentException e) { + log.error("Could not render the transition.", e); + } + } + + /** + * Determine if the image will fit on the given page. + * + * @param pageSize the page size + * @param wImage the width of the thing to be printed + * @param hImage the height of the thing to be printed + * @return true if the thing to be printed will fit on a single page + */ + private boolean fitsOnOnePage(Dimension pageSize, double wImage, double hImage) { + double wPage = pageSize.getWidth(); + double hPage = pageSize.getHeight(); + + int wRatio = (int) Math.ceil(wImage / wPage); + int hRatio = (int) Math.ceil(hImage / hPage); + + return wRatio <= 1.0d && hRatio <= 1.0d; + } + + /** + * Print the transition. + * + * @param theTransition the printable transition + */ + private void printOnOnePage(final AbstractPrintableTransition theTransition) { + Dimension d = getPageSize(); + PdfContentByte cb = writer.getDirectContent(); + Graphics2D g2 = cb.createGraphics(d.width, d.height); + theTransition.print(g2); + g2.dispose(); + document.newPage(); + } + + /** + * Get the dimensions of the paper page. + * + * @return an internal Dimension + */ + protected Dimension getPageSize() { + return new Dimension(document.getPageSize().getWidth(), + document.getPageSize().getHeight()); + } + + /** + * Convenience class to model a dimension. + */ + class Dimension { + /** + * Width, in points. + */ + public float width; + /** + * Height, in points. + */ + public float height; + + /** + * Constructor. + * + * @param w width + * @param h height + */ + public Dimension(float w, float h) { + width = w; + height = h; + } + + /** + * Get the width. + * + * @return the width + */ + public float getWidth() { + return width; + } + + /** + * Get the height. + * + * @return the height + */ + public float getHeight() { + return height; + } + } +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java b/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java index 53dc895d..791057c7 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java @@ -1,13 +1,13 @@ package net.sf.openrocket.gui.rocketfigure; -import java.awt.Shape; -import java.awt.geom.Path2D; -import java.util.ArrayList; - import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; +import java.awt.*; +import java.awt.geom.Path2D; +import java.util.ArrayList; + public class SymmetricComponentShapes extends RocketComponentShapes { private static final int MINPOINTS = 91; @@ -16,9 +16,14 @@ public class SymmetricComponentShapes extends RocketComponentShapes { // TODO: HIGH: adaptiveness sucks, remove it. // TODO: LOW: Uses only first component of cluster (not currently clusterable) - - public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation) { + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + return getShapesSide(component, transformation, S); + } + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, final double scaleFactor) { net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent) component; int i; @@ -77,14 +82,14 @@ public class SymmetricComponentShapes extends RocketComponentShapes { // TODO: LOW: curved path instead of linear Path2D.Double path = new Path2D.Double(); - path.moveTo(points.get(len - 1).x * S, points.get(len - 1).y * S); + path.moveTo(points.get(len - 1).x * scaleFactor, points.get(len - 1).y * scaleFactor); for (i = len - 2; i >= 0; i--) { - path.lineTo(points.get(i).x * S, points.get(i).y * S); + path.lineTo(points.get(i).x * scaleFactor, points.get(i).y * scaleFactor); } for (i = 0; i < len; i++) { - path.lineTo(points.get(i).x * S, -points.get(i).y * S); + path.lineTo(points.get(i).x * scaleFactor, -points.get(i).y * scaleFactor); } - path.lineTo(points.get(len - 1).x * S, points.get(len - 1).y * S); + path.lineTo(points.get(len - 1).x * scaleFactor, points.get(len - 1).y * scaleFactor); path.closePath(); //s[len] = path; diff --git a/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java b/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java index 205cd502..2312c1bd 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java @@ -1,21 +1,26 @@ package net.sf.openrocket.gui.rocketfigure; -import java.awt.Shape; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Path2D; -import java.awt.geom.Rectangle2D; - import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Transformation; +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; + public class TransitionShapes extends RocketComponentShapes { // TODO: LOW: Uses only first component of cluster (not currently clusterable). - - public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation) { + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation) { + return getShapesSide(component, transformation, S); + } + + public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, final double scaleFactor) { net.sf.openrocket.rocketcomponent.Transition transition = (net.sf.openrocket.rocketcomponent.Transition)component; Shape[] mainShapes; @@ -29,15 +34,15 @@ public class TransitionShapes extends RocketComponentShapes { toAbsolute(Coordinate.NUL)[0]); Path2D.Float path = new Path2D.Float(); - path.moveTo(start.x*S, r1*S); - path.lineTo((start.x+length)*S, r2*S); - path.lineTo((start.x+length)*S, -r2*S); - path.lineTo(start.x*S, -r1*S); + path.moveTo(start.x* scaleFactor, r1* scaleFactor); + path.lineTo((start.x+length)* scaleFactor, r2* scaleFactor); + path.lineTo((start.x+length)* scaleFactor, -r2* scaleFactor); + path.lineTo(start.x* scaleFactor, -r1* scaleFactor); path.closePath(); mainShapes = new Shape[] { path }; } else { - mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation); + mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, scaleFactor); } Rectangle2D.Double shoulder1=null, shoulder2=null; @@ -48,7 +53,7 @@ public class TransitionShapes extends RocketComponentShapes { toAbsolute(Coordinate.NUL)[0]); double r = transition.getForeShoulderRadius(); double l = transition.getForeShoulderLength(); - shoulder1 = new Rectangle2D.Double((start.x-l)*S, -r*S, l*S, 2*r*S); + shoulder1 = new Rectangle2D.Double((start.x-l)* scaleFactor, -r* scaleFactor, l* scaleFactor, 2*r* scaleFactor); arrayLength++; } if (transition.getAftShoulderLength() > 0.0005) { @@ -56,7 +61,7 @@ public class TransitionShapes extends RocketComponentShapes { toAbsolute(new Coordinate(transition.getLength()))[0]); double r = transition.getAftShoulderRadius(); double l = transition.getAftShoulderLength(); - shoulder2 = new Rectangle2D.Double(start.x*S, -r*S, l*S, 2*r*S); + shoulder2 = new Rectangle2D.Double(start.x* scaleFactor, -r* scaleFactor, l* scaleFactor, 2*r* scaleFactor); arrayLength++; } if (shoulder1==null && shoulder2==null) -- 2.47.2