+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
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...
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
--- /dev/null
+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
+ * <a href="http://www.rocketshoppe.com/info/Transitions.pdf">The Properties of
+ * Model Rocket Body Tube Transitions, by J.R. Brohm</a>
+ *
+ * @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);
+ }
+}
--- /dev/null
+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.
+ * <p/>
+ */
+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<BodyTube, java.util.List<ExternalComponent>> 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<BodyTube, java.util.List<ExternalComponent>> init(Rocket component) {
+ Iterator<RocketComponent> iter = component.iterator(false);
+ Map<BodyTube, java.util.List<ExternalComponent>> results = new LinkedHashMap<BodyTube, List<ExternalComponent>>();
+ 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<ExternalComponent> list = results.get(current);
+ if (list == null && current != null) {
+ list = new ArrayList<ExternalComponent>();
+ 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;
+ }
+
+ /**
+ * <pre>
+ * ---------------------- 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.
+ * </pre>
+ *
+ * @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<ExternalComponent> 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<ExternalComponent> 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<ExternalComponent> componentList) {
+ Collections.sort(componentList, new Comparator<ExternalComponent>() {
+ @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);
+ }
+
+
+}
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
-import java.awt.Graphics2D;
+import java.awt.*;
import java.awt.image.BufferedImage;
/**
*/
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();
}
}
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.
*/
}
return null;
}
+
+ /**
+ * Get a list of ordered enum values that do not have stage affinity.
+ *
+ * @return a list of OpenRocketPrintable
+ */
+ public static List<OpenRocketPrintable> getUnstaged() {
+ List<OpenRocketPrintable> unstaged = new ArrayList<OpenRocketPrintable>();
+ OpenRocketPrintable[] values = values();
+ for (OpenRocketPrintable value : values) {
+ if (!value.isStageSpecific()) {
+ unstaged.add(value);
+ }
+ }
+ return unstaged;
+ }
}
*/
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;
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
* @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<PrintableContext> toBePrinted, OutputStream outputFile,
PrintSettings settings) {
idoc.newPage();
break;
case FIN_TEMPLATE:
- final FinSetVisitorStrategy finWriter = new FinSetVisitorStrategy(idoc,
+ final FinSetPrintStrategy finWriter = new FinSetPrintStrategy(idoc,
writer,
stages);
finWriter.writeToDocument(doc.getRocket());
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) {
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;
/**
* 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.
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;
}
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);
}
/**
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());
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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.
+ * <p/>
+ * 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);
+ }
+
+}
*/
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.
}
}
}
- toAddTo.add(new CheckBoxNode(OpenRocketPrintable.DESIGN_REPORT.getDescription(),
+
+ List<OpenRocketPrintable> 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
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+/*
+ * FinSetPrintStrategy.java
+ */
+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.ITextHelper;
+import net.sf.openrocket.gui.print.PrintableFinSet;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+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 fin templates.
+ */
+public class FinSetPrintStrategy {
+
+ /**
+ * 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<Integer> stages;
+
+ /**
+ * Constructor.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStages The stages to be printed by this strategy
+ */
+ public FinSetPrintStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStages) {
+ document = doc;
+ writer = theWriter;
+ stages = theStages;
+ }
+
+ /**
+ * Recurse through the given rocket component.
+ *
+ * @param root the root component; all children will be printed recursively
+ */
+ public void writeToDocument (final RocketComponent root) {
+ List<RocketComponent> rc = root.getChildren();
+ goDeep(rc);
+ }
+
+
+ /**
+ * Recurse through the given rocket component.
+ *
+ * @param theRc an array of rocket components; all children will be printed recursively
+ */
+ protected void goDeep (final List<RocketComponent> theRc) {
+ for (RocketComponent rocketComponent : theRc) {
+ if (rocketComponent instanceof FinSet) {
+ printFinSet((FinSet) rocketComponent);
+ }
+ else if (rocketComponent.getChildCount() > 0) {
+ goDeep(rocketComponent.getChildren());
+ }
+ }
+ }
+
+ /**
+ * The core behavior of this strategy.
+ *
+ * @param finSet the object to extract info about; a graphical image of the fin shape is drawn to the document
+ */
+ private void printFinSet(final FinSet finSet) {
+ if (shouldPrintStage(finSet.getStageNumber())) {
+ try {
+ PrintableFinSet pfs = new PrintableFinSet(finSet);
+
+ java.awt.Dimension finSize = pfs.getSize();
+ final Dimension pageSize = getPageSize();
+ if (fitsOnOnePage(pageSize, finSize.getWidth(), finSize.getHeight())) {
+ printOnOnePage(pfs);
+ }
+ else {
+ BufferedImage image = (BufferedImage) pfs.createImage();
+ ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()),
+ document, writer, image);
+ }
+ document.newPage();
+ }
+ catch (DocumentException e) {
+ log.error("Could not render fin.", e);
+ }
+ }
+ }
+
+ /**
+ * Determine if the strategy's set of stage numbers (to print) contains the specified stage.
+ *
+ * @param stageNumber a stage number
+ *
+ * @return true if the strategy contains the stage number provided
+ */
+ public boolean shouldPrintStage(int stageNumber) {
+ if (stages == null || stages.isEmpty()) {
+ return false;
+ }
+
+ for (final Integer stage : stages) {
+ if (stage == stageNumber) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 fin set.
+ *
+ * @param thePfs the printable fin set
+ */
+ private void printOnOnePage (final PrintableFinSet thePfs) {
+ Dimension d = getPageSize();
+ PdfContentByte cb = writer.getDirectContent();
+ Graphics2D g2 = cb.createGraphics(d.width, d.height);
+ thePfs.print(g2);
+ g2.dispose();
+ }
+
+ /**
+ * 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;
+ }
+ }
+}
+++ /dev/null
-/*
- * FinSetVisitorStrategy.java
- */
-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.ITextHelper;
-import net.sf.openrocket.gui.print.PrintableFinSet;
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-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 fin templates.
- */
-public class FinSetVisitorStrategy {
-
- /**
- * 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<Integer> stages;
-
- /**
- * Constructor.
- *
- * @param doc The iText document
- * @param theWriter The direct iText writer
- * @param theStagesToVisit The stages to be visited by this strategy
- */
- public FinSetVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- document = doc;
- writer = theWriter;
- stages = theStagesToVisit;
- }
-
- /**
- * Recurse through the given rocket component.
- *
- * @param root the root component; all children will be visited recursively
- */
- public void writeToDocument (final RocketComponent root) {
- List<RocketComponent> rc = root.getChildren();
- goDeep(rc);
- }
-
-
- /**
- * Recurse through the given rocket component.
- *
- * @param theRc an array of rocket components; all children will be visited recursively
- */
- protected void goDeep (final List<RocketComponent> theRc) {
- for (RocketComponent rocketComponent : theRc) {
- if (rocketComponent instanceof FinSet) {
- doVisit((FinSet)rocketComponent);
- }
- else if (rocketComponent.getChildCount() > 0) {
- goDeep(rocketComponent.getChildren());
- }
- }
- }
-
- /**
- * The core behavior of this visitor.
- *
- * @param visitable 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())) {
- try {
- PrintableFinSet pfs = new PrintableFinSet(visitable);
-
- java.awt.Dimension finSize = pfs.getSize();
- final Dimension pageSize = getPageSize();
- if (fitsOnOnePage(pageSize, finSize.getWidth(), finSize.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 fin.", e);
- }
- }
- }
-
- /**
- * Determine if the visitor 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
- */
- public boolean shouldVisitStage (int stageNumber) {
- if (stages == null || stages.isEmpty()) {
- return false;
- }
-
- for (final Integer stage : stages) {
- if (stage == stageNumber) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * 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 fin set.
- *
- * @param thePfs the printable fin set
- */
- private void printOnOnePage (final PrintableFinSet thePfs) {
- Dimension d = getPageSize();
- PdfContentByte cb = writer.getDirectContent();
- Graphics2D g2 = cb.createGraphics(d.width, d.height);
- thePfs.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;
- }
- }
-}
--- /dev/null
+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<Integer> 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<Integer> 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<RocketComponent> 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<RocketComponent> 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;
+ }
+ }
+}
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;
// 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;
// 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;
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;
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;
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) {
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)