--- /dev/null
+package net.sf.openrocket.gui.print;
+
+import net.sf.openrocket.gui.print.visitor.CenteringRingStrategy;
+import net.sf.openrocket.rocketcomponent.CenteringRing;
+import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
+import net.sf.openrocket.rocketcomponent.InnerTube;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.Coordinate;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class creates a renderable centering ring. It depends only on AWT/Swing and can be called from other actors
+ * (like iText handlers) to render the centering ring on different graphics contexts.
+ */
+public class PrintableCenteringRing extends AbstractPrintable<CenteringRing> {
+ /**
+ * If the component to be drawn is a centering ring, save a reference to it.
+ */
+ private CenteringRing target;
+
+ /**
+ * The line length of the cross hairs.
+ */
+ private final int lineLength = 10;
+
+ /**
+ * A set of the inner 'holes'. At least one, but will have many if clustered.
+ */
+ private Set<CenteringRingStrategy.Dimension> innerCenterPoints = new HashSet<CenteringRingStrategy.Dimension>();
+
+ /**
+ * Construct a simple, non-clustered, printable centering ring, or if the motor mount represents a clustered
+ * configuration then get the cluster points to create the centering ring.
+ *
+ * @param theRing the component to print
+ * @param theMotorMount the motor mount if clustered, else null
+ */
+ private PrintableCenteringRing(CenteringRing theRing, InnerTube theMotorMount) {
+ super(false, theRing);
+ if (theMotorMount == null || theMotorMount.getClusterConfiguration().equals(ClusterConfiguration.SINGLE)) {
+ //Single motor.
+ final float v = (float) PrintUnit.METERS.toPoints(target.getOuterRadius());
+ innerCenterPoints.add(
+ new CenteringRingStrategy.Dimension(v, v,
+ (float) PrintUnit.METERS.toPoints((target.getInnerRadius()))));
+ }
+ else {
+ List<Coordinate> coords = theMotorMount.getClusterPoints();
+ List<Coordinate> points = new ArrayList<Coordinate>();
+ for (Coordinate coordinate : coords) {
+ points.add(coordinate.setX(theMotorMount.getOuterRadius()));
+ }
+ populateCenterPoints(points);
+ }
+ }
+
+ /**
+ * Constructor for a clustered centering ring. This version is for a "split cluster", where each motor mount tube
+ * is a distinct entity.
+ *
+ * @param theRing the centering ring component
+ * @param theMotorMounts a list of the motor mount tubes that are physically supported by the centering ring
+ */
+ private PrintableCenteringRing(CenteringRing theRing, List<InnerTube> theMotorMounts) {
+ super(false, theRing);
+ List<Coordinate> points = new ArrayList<Coordinate>();
+ //Transform the radial positions of the tubes.
+ for (InnerTube it : theMotorMounts) {
+ if (it.getClusterCount() > 1) {
+ List<Coordinate> c = it.getClusterPoints();
+ for (Coordinate coordinate : c) {
+ points.add(coordinate.setX(it.getOuterRadius()));
+ }
+ }
+ else {
+ double y = it.getRadialShiftY();
+ double z = it.getRadialShiftZ();
+ Coordinate coordinate = new Coordinate(it.getOuterRadius(), y, z);
+ points.add(coordinate);
+ }
+ }
+ populateCenterPoints(points);
+ }
+
+ /**
+ * Factory method to create a printable centering ring.
+ *
+ * @param theRing the component to print
+ * @param theMotorMounts the motor mount if clustered, else null
+ */
+ public static PrintableCenteringRing create(CenteringRing theRing, List<InnerTube> theMotorMounts) {
+ if (theMotorMounts == null) {
+ return new PrintableCenteringRing(theRing, (InnerTube) null);
+ }
+ else if (theMotorMounts.size() <= 1) {
+ return new PrintableCenteringRing(theRing, theMotorMounts.isEmpty() ? null : theMotorMounts.get(0));
+ }
+ else {
+ return new PrintableCenteringRing(theRing, theMotorMounts);
+ }
+ }
+
+ /**
+ * Initialize the set of center points for each motor mount tube, based on the tube coordinates.
+ *
+ * @param theCoords the list of tube coordinates; each coordinate is in the OR units (meters) and must be
+ * transformed to the printing (points) coordinate system
+ */
+ private void populateCenterPoints(final List<Coordinate> theCoords) {
+ float radius = (float) PrintUnit.METERS.toPoints(target.getOuterRadius());
+ for (Coordinate coordinate : theCoords) {
+ innerCenterPoints.add(new CenteringRingStrategy.Dimension(
+ (float) PrintUnit.METERS.toPoints(coordinate.y) + radius, //center point x
+ (float) PrintUnit.METERS.toPoints(coordinate.z) + radius, //center point y
+ (float) PrintUnit.METERS.toPoints(coordinate.x))); //radius of motor mount
+ }
+ }
+
+ /**
+ * @param component the centering ring component
+ */
+ @Override
+ protected void init(final CenteringRing component) {
+
+ target = component;
+
+ double radius = target.getOuterRadius();
+ setSize((int) PrintUnit.METERS.toPoints(2 * radius),
+ (int) PrintUnit.METERS.toPoints(2 * radius));
+ }
+
+ /**
+ * Draw a centering ring.
+ *
+ * @param g2 the graphics context
+ */
+ @Override
+ protected void draw(Graphics2D g2) {
+ double radius = PrintUnit.METERS.toPoints(target.getOuterRadius());
+
+ Color original = g2.getBackground();
+ Shape outerCircle = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
+ g2.setColor(Color.lightGray);
+ g2.fill(outerCircle);
+ g2.setColor(Color.black);
+ g2.draw(outerCircle);
+
+ for (CenteringRingStrategy.Dimension next : innerCenterPoints) {
+ drawInnerCircle(g2, next.getWidth(), next.getHeight(), next.getBreadth());
+ }
+ g2.setColor(original);
+ }
+
+ /**
+ * Draw one inner circle, representing the motor mount tube, with cross hairs in the center.
+ *
+ * @param g2 the graphics context
+ * @param theCenterX the center x in points
+ * @param theCenterY the center y in points
+ */
+ private void drawInnerCircle(final Graphics2D g2, final double theCenterX, final double theCenterY,
+ final double innerRadius) {
+ Shape innerCircle = new Ellipse2D.Double(theCenterX - innerRadius, theCenterY - innerRadius, innerRadius * 2, innerRadius * 2);
+ g2.setColor(Color.white);
+ g2.fill(innerCircle);
+ g2.setColor(Color.black);
+ g2.draw(innerCircle);
+
+ drawCross(g2, (int) theCenterX, (int) theCenterY, lineLength, lineLength);
+ }
+
+ /**
+ * Draw the center cross-hair.
+ *
+ * @param g the graphics context
+ * @param x the x coordinate of the center point
+ * @param y the y coordinate of the center point
+ * @param width the width in pixels of the horizontal hair
+ * @param height the width in pixels of the vertical hair
+ */
+ private void drawCross(Graphics g, int x, int y, int width, int height) {
+ g.setColor(Color.black);
+ ((Graphics2D) g).setStroke(thinStroke);
+ g.drawLine(x - width / 2, y, x + width / 2, y);
+ g.drawLine(x, y - height / 2, x, y + height / 2);
+ }
+
+}