import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.print.PDFPrintStreamDoc;
+import net.sf.openrocket.gui.print.PrintServiceDialog;
import net.sf.openrocket.gui.print.PrintUtilities;
-import sun.print.ServiceDialog;
-import javax.print.DocFlavor;
-import javax.print.DocPrintJob;
-import javax.print.PrintException;
-import javax.print.PrintService;
-import javax.print.PrintServiceLookup;
+import javax.print.*;
import javax.print.attribute.Attribute;
import javax.print.attribute.AttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Destination;
import javax.print.attribute.standard.Fidelity;
-import javax.swing.JDialog;
-import javax.swing.JMenu;
-import javax.swing.JTabbedPane;
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Dialog;
-import java.awt.GraphicsConfiguration;
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.awt.HeadlessException;
-import java.awt.Rectangle;
+import javax.swing.*;
+import java.awt.*;
import java.io.ByteArrayOutputStream;
/**
/**
* The service UI dialog.
*/
- private ServiceDialog dialog;
+ private PrintServiceDialog dialog;
/**
* A javax doc flavor specific for printing PDF documents.
if (ps != null) {
DocPrintJob dpj = ps.createPrintJob();
try {
- System.err.println(attrs.size());
ByteArrayOutputStream baos = panel.generateReport();
dpj.print(new PDFPrintStreamDoc(baos, null), attrs);
}
catch (PrintException e) {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ e.printStackTrace();
+ //dgp
}
}
}
getLocalGraphicsEnvironment().getDefaultScreenDevice().
getDefaultConfiguration().getBounds() : gc.getBounds();
- dialog = new ServiceDialog(gc,
+ dialog = new PrintServiceDialog(gc,
x + gcBounds.x,
y + gcBounds.y,
services, defaultIndex,
dialog.setVisible(true);
- if (dialog.getStatus() == ServiceDialog.APPROVE) {
+ if (dialog.getStatus() == PrintServiceDialog.APPROVE) {
PrintRequestAttributeSet newas = dialog.getAttributes();
Class dstCategory = Destination.class;
Class fdCategory = Fidelity.class;
/**
* Removes any attributes from the given AttributeSet that are unsupported by the given PrintService/DocFlavor
* combination.
- *
+ *
* @param ps the print service for which unsupported attributes will be determined
* @param flavor the document flavor; PDF in our case
* @param aset the set of attributes requested
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaSizeName;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JColorChooser;
-import javax.swing.JComponent;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.UIManager;
+import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
-import java.awt.Color;
-import java.awt.Desktop;
+import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.util.Iterator;
/**
- * This class isolates the Swing components used to create a panel that is added to the standard Java print dialog.
+ * This class isolates the Swing components used to create a panel that is added to a standard Java print dialog.
*/
public class PrintPanel extends JPanel implements TreeSelectionListener {
private static final LogHelper log = Application.getLogger();
+ private static final String TAB_TITLE = "Rocket";
+ private static final String SETTINGS_BUTTON_TEXT = "Settings";
+ private static final String PREVIEW_BUTTON_TEXT = "Preview";
+ private static final String SAVE_AS_PDF_BUTTON_TEXT = "Save as PDF";
+ private static final String SHOW_BY_STAGE = "Show By Stage";
+
private final RocketPrintTree stagedTree;
private final RocketPrintTree noStagedTree;
private OpenRocketDocument rocDoc;
JButton previewButton;
JButton saveAsPDF;
-
+
/**
* Constructor.
*
currentTree = stagedTree;
final JScrollPane scrollPane = new JScrollPane(stagedTree);
- add(scrollPane, "width 475!, wrap");
+ add(scrollPane, "width 416!, wrap");
- final JCheckBox sortByStage = new JCheckBox("Show By Stage");
+ final JCheckBox sortByStage = new JCheckBox(SHOW_BY_STAGE);
sortByStage.setEnabled(stages > 1);
sortByStage.setSelected(stages > 1);
sortByStage.addActionListener(new ActionListener() {
});
add(sortByStage, "wrap");
- saveAsPDF = new JButton("Save as PDF");
+ saveAsPDF = new JButton(SAVE_AS_PDF_BUTTON_TEXT);
saveAsPDF.addActionListener(new ActionListener() {
@Override
public void actionPerformed (ActionEvent e) {
});
add(saveAsPDF, "span 2, tag save");
- previewButton = new JButton("Preview");
+ previewButton = new JButton(PREVIEW_BUTTON_TEXT);
previewButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed (ActionEvent e) {
});
add(previewButton, "x 150");
- JButton settingsButton = new JButton("Settings");
+ JButton settingsButton = new JButton(SETTINGS_BUTTON_TEXT);
settingsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed (ActionEvent e) {
settingsDialog.setVisible(true);
}
});
- add(settingsButton, "x 400");
+ add(settingsButton, "x 340");
expandAll(currentTree, true);
if (currentTree != noStagedTree) {
* @return a title
*/
public String getTitle () {
- return "Rocket";
+ return TAB_TITLE;
}
- @Override
+ @Override
public void valueChanged (final TreeSelectionEvent e) {
final TreePath path = e.getNewLeadSelectionPath();
if (path != null){
*/
public void expandAll (RocketPrintTree theTree, boolean expand) {
TreeNode root = (TreeNode) theTree.getModel().getRoot();
- // Traverse theTree from root
+ // Traverse theTree from root
expandAll(theTree, new TreePath(root), expand);
}
*/
private void expandAll (RocketPrintTree theTree, TreePath parent, boolean expand) {
theTree.addSelectionPath(parent);
- // Traverse children
+ // Traverse children
TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() >= 0) {
for (Enumeration e = node.children(); e.hasMoreElements();) {
expandAll(theTree, path, expand);
}
}
- // Expansion or collapse must be done bottom-up
+ // Expansion or collapse must be done bottom-up
if (expand) {
theTree.expandPath(parent);
}
*/
package net.sf.openrocket.gui.print;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Paragraph;
+import com.itextpdf.text.*;
import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.BaseFont;
-import com.itextpdf.text.pdf.DefaultFontMapper;
-import com.itextpdf.text.pdf.PdfContentByte;
-import com.itextpdf.text.pdf.PdfPCell;
-import com.itextpdf.text.pdf.PdfPTable;
-import com.itextpdf.text.pdf.PdfWriter;
+import com.itextpdf.text.pdf.*;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.figureelements.FigureElement;
import net.sf.openrocket.gui.figureelements.RocketInfo;
-import net.sf.openrocket.gui.print.visitor.BaseVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.MotorMountVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.StageVisitorStrategy;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.*;
import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Prefs;
-import java.awt.Graphics2D;
+import java.awt.*;
import java.io.IOException;
import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
import java.util.List;
/**
* <p/>
* </pre>
*/
-public class DesignReport extends BaseVisitorStrategy {
+public class DesignReport {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
/**
* The OR Document.
final RocketPanel panel;
/**
- * A stage visitor.
+ * The iText document.
*/
- private StageVisitorStrategy svs = new StageVisitorStrategy();
+ protected Document document;
+
+ /** The displayed strings. */
+ private static final String STAGES = "Stages: ";
+ private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
+ private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
+ private static final String MASS_EMPTY = "Mass (Empty): ";
+ private static final String STABILITY = "Stability: ";
+ private static final String CG = "Cg: ";
+ private static final String CP = "Cp: ";
+ private static final String MOTOR = "Motor";
+ private static final String AVG_THRUST = "Avg Thrust";
+ private static final String BURN_TIME = "Burn Time";
+ private static final String MAX_THRUST = "Max Thrust";
+ private static final String TOTAL_IMPULSE = "Total Impulse";
+ private static final String THRUST_TO_WT = "Thrust to Wt";
+ private static final String PROPELLANT_WT = "Propellant Wt";
+ private static final String SIZE = "Size";
+ private static final String ALTITUDE = "Altitude";
+ private static final String FLIGHT_TIME = "Flight Time";
+ private static final String TIME_TO_APOGEE = "Time to Apogee";
+ private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
+ private static final String MAX_VELOCITY = "Max Velocity";
+ private static final String LANDING_VELOCITY = "Landing Velocity";
+ private static final String ROCKET_DESIGN = "Rocket Design";
+ private static final double GRAVITY_CONSTANT = 9.80665d;
/**
* Constructor.
* @param theIDoc the iText document
*/
public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
- super(theIDoc, null);
+ document = theIDoc;
rocketDocument = theRocDoc;
panel = new RocketPanel(rocketDocument);
}
*
* @param writer a direct byte writer
*/
- public void print (PdfWriter writer) {
+ public void writeToDocument (PdfWriter writer) {
if (writer == null) {
return;
}
int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
- PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, "Rocket Design");
+ PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
Rocket rocket = rocketDocument.getRocket();
final Configuration configuration = rocket.getDefaultConfiguration();
BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not set font.", e);
}
catch (IOException e) {
- e.printStackTrace();
+ log.error("Could not create font.", e);
}
int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
.toPoints(1)));
canvas.showText(rocketDocument.getRocket().getName());
- canvas.newlineShowText("Stages: ");
+ canvas.newlineShowText(STAGES);
canvas.showText("" + rocket.getStageCount());
if (configuration.hasMotors()) {
- canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
+ if (configuration.getStageCount() > 1) {
+ canvas.newlineShowText(MASS_WITH_MOTORS);
+ }
+ else {
+ canvas.newlineShowText(MASS_WITH_MOTOR);
+ }
}
else {
- canvas.newlineShowText("Mass (Empty): ");
+ canvas.newlineShowText(MASS_EMPTY);
}
canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
- canvas.newlineShowText("Stability: ");
+ canvas.newlineShowText(STABILITY);
canvas.showText(text.getStability());
- canvas.newlineShowText("Cg: ");
+ canvas.newlineShowText(CG);
canvas.showText(text.getCg());
- canvas.newlineShowText("Cp: ");
+ canvas.newlineShowText(CP);
canvas.showText(text.getCp());
canvas.endText();
List<Double> stages = getStageWeights(rocket);
-
for (int j = 0; j < mids.length; j++) {
String mid = mids[j];
if (mid != null) {
- MotorMountVisitorStrategy mmvs = new MotorMountVisitorStrategy(document, mid);
- rocket.accept(new ComponentVisitor(mmvs));
+ final List<Motor> motorList = getMotorList(rocket, mid);
+
PdfPTable parent = new PdfPTable(2);
parent.setWidthPercentage(100);
parent.setHorizontalAlignment(Element.ALIGN_LEFT);
leading = 25;
}
addFlightData(rocket, mid, parent, leading);
- addMotorData(mmvs.getMotors(), parent, stages);
+ addMotorData(motorList, parent, stages);
document.add(parent);
}
}
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not modify document.", e);
+ }
+ }
+
+ /**
+ * Get the motor list for all motor mounts.
+ *
+ * @param theRocket the rocket object
+ * @param theMid the motor id
+ *
+ * @return a list of Motor
+ */
+ private List<Motor> getMotorList (final Rocket theRocket, final String theMid) {
+ Iterator<RocketComponent> components = theRocket.deepIterator();
+ final List<Motor> motorList = new ArrayList<Motor>();
+ while (components.hasNext()) {
+ RocketComponent rocketComponent = components.next();
+ if (rocketComponent instanceof MotorMount) {
+ MotorMount mm = (MotorMount) rocketComponent;
+ final Motor motor = mm.getMotor(theMid);
+ if (motor != null) {
+ motorList.add(motor);
+ }
+ }
}
+ return motorList;
}
/**
motorTable.setWidthPercentage(68);
motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
- final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
+ final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
final int mPad = 10;
motorCell.setPaddingLeft(mPad);
motorTable.addCell(motorCell);
- motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Thrust to Wt", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Propellant Wt", PdfPCell.BOTTOM));
- motorTable.addCell(ITextHelper.createCell("Size", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
DecimalFormat df = new DecimalFormat("#,##0.0#");
for (int i = 0; i < motors.size(); i++) {
.UNITS_IMPULSE
.getDefaultUnit().toString(), border));
double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
- .getLaunchCG().weight * 9.80665));
+ .getLaunchCG().weight * GRAVITY_CONSTANT));
motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
FlightData flight = null;
if (theRocket.getMotorConfigurationIDs().length > 1) {
- Rocket duplicate= theRocket.copyWithOriginalID();
+ Rocket duplicate = theRocket.copyWithOriginalID();
Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
simulation.getConditions().setMotorConfigurationID(mid);
DecimalFormat df = new DecimalFormat("#,##0.0#");
- final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
+ final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
cell.setUseBorderPadding(false);
cell.setBorderWidthTop(0f);
labelTable.addCell(cell);
labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
2, 2));
- labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
2));
- labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
2));
- labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(
flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
- labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
2, 2));
- labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(
flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
parent.addCell(c);
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not add flight data to document.", e);
}
}
}
}
/**
- * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
- * weight.
+ * From a list of Stages get the stage masses and convert to weight.
*
* @param rocket the rocket
*
* @return a sorted list of Stage weights (mass * gravity), in Newtons
*/
private List<Double> getStageWeights (Rocket rocket) {
- rocket.accept(new ComponentVisitor(svs));
- svs.close();
- List<Double> stages = svs.getStages();
+ List<Double> stages = getStageMasses(rocket);
+
for (int i = 0; i < stages.size(); i++) {
Double stage = stages.get(i);
- stages.set(i, stage * 9.80665);
+ stages.set(i, stage * GRAVITY_CONSTANT);
+ }
+ return stages;
+ }
+
+ /**
+ * From a list of Stages get the stage masses.
+ *
+ * @param rocket the rocket
+ *
+ * @return a sorted list of Stage masses
+ */
+ private List<Double> getStageMasses (final Rocket rocket) {
+ Double mass = 0d;
+
+ List<Double> stages = new ArrayList<Double>();
+ Iterator<RocketComponent> iter = rocket.deepIterator();
+ while (iter.hasNext()) {
+ RocketComponent rocketComponent = iter.next();
+ if (rocketComponent instanceof Stage) {
+ if (mass > 0d) {
+ stages.add(mass);
+ mass = 0d;
+ }
+ }
+ else {
+ mass += rocketComponent.getMass();
+ }
+ }
+ if (mass > 0d) {
+ stages.add(mass);
}
return stages;
}
import javax.print.DocFlavor;
import javax.print.attribute.AttributeSetUtilities;
import javax.print.attribute.DocAttributeSet;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
+import java.io.*;
/**
+ * This class implements a javax Doc specifically for PDF printing. All reports in OpenRocket are PDF (iText) based.
*/
public class PDFPrintStreamDoc implements Doc {
+ /** The source stream of the PDF document. */
private InputStream stream;
+
+ /** The document's attributes. */
private DocAttributeSet attributeSet;
+ /**
+ * Constructor.
+ *
+ * @param ostream an output stream representing the pdf doc
+ * @param attributes the attributes of the document
+ */
public PDFPrintStreamDoc (ByteArrayOutputStream ostream, DocAttributeSet attributes) {
stream = new ByteArrayInputStream(ostream.toByteArray());
if (attributes != null) {
}
}
+ /**
+ * Flavor is PDF.
+ *
+ * @return PDF flavor
+ */
+ @Override
public DocFlavor getDocFlavor () {
return DocFlavor.INPUT_STREAM.PDF;
}
+ @Override
public DocAttributeSet getAttributes () {
return attributeSet;
}
/* Since the data is to be supplied as an InputStream delegate to
* getStreamForBytes().
*/
-
+ @Override
public Object getPrintData () throws IOException {
return getStreamForBytes();
}
+ /**
+ * Intentionally null since the flavor is PDF.
+ *
+ * @return null
+ */
+ @Override
public Reader getReaderForText () {
return null;
}
/* Return the print data as an InputStream.
* Always return the same instance.
*/
-
+ @Override
public InputStream getStreamForBytes () throws IOException {
return stream;
}
import java.util.Map;
/**
- * Various mappings of paper sizes.
+ * Various mappings of paper sizes and their names.
*/
public class PaperSize {
-
+
+ /** Map of name to MediaSizeName instance. */
private static Map<String, MediaSizeName> paperNames = new HashMap<String, MediaSizeName>();
+ /** Map of identifying name to displayable name. */
+ private static Map<String, String> displayableNames = new HashMap<String, String>();
+ /** Map of MediaSizeName to rectangle, which defines the paper size. */
private static Map<MediaSizeName, Rectangle> paperItext = new HashMap<MediaSizeName, Rectangle>();
-
+
+ /**
+ * Init.
+ */
static {
populateNameMap();
populateITextSizeMap();
+ populateDisplayableNameMap();
}
+
+ /** Disallow construction. */
private PaperSize() {}
-
+
+ /**
+ * Map an identifying paper name to it's corresponding MediaSizeName.
+ *
+ * @param name a paper name
+ *
+ * @return the associated MediaSizeName (or null if not found).
+ */
public static MediaSizeName convert(String name) {
return paperNames.get(name);
}
+ /**
+ * Map a MediaSizeName to it's size Rectangle.
+ *
+ * @param name a paper name
+ *
+ * @return a Rectangle or null
+ */
public static Rectangle convert(MediaSizeName name) {
return paperItext.get(name);
}
-
+
+ /**
+ * Map an identifying paper name to a displayable name (usually it's common name).
+ *
+ * @param name a paper name
+ *
+ * @return a displayable name
+ */
+ public static String toDisplayable(String name) {
+ return displayableNames.get(name);
+ }
+
private static void populateNameMap() {
paperNames.put("iso-a0", MediaSizeName.ISO_A0);
paperNames.put("iso-a1", MediaSizeName.ISO_A1);
paperItext.put(MediaSizeName.D, new RectangleReadOnly(PrintUnit.INCHES.toPoints(22), PrintUnit.INCHES.toPoints(34)));
paperItext.put(MediaSizeName.E, new RectangleReadOnly(PrintUnit.INCHES.toPoints(34), PrintUnit.INCHES.toPoints(44)));
}
-
+
+ /**
+ * Create a name map from standard to displayable names
+ */
+ private static void populateDisplayableNameMap() {
+ displayableNames.put("iso-a0", "A0");
+ displayableNames.put("iso-a1", "A1");
+ displayableNames.put("iso-a2", "A2");
+ displayableNames.put("iso-a3", "A3");
+ displayableNames.put("iso-a4", "A4");
+ displayableNames.put("iso-a5", "A5");
+ displayableNames.put("iso-a6", "A6");
+ displayableNames.put("iso-a7", "A7");
+ displayableNames.put("iso-a8", "A8");
+ displayableNames.put("iso-a9", "A9");
+ displayableNames.put("iso-a10", "A10");
+ displayableNames.put("iso-b0", "B0");
+ displayableNames.put("iso-b1", "B1");
+ displayableNames.put("iso-b2", "B2");
+ displayableNames.put("iso-b3", "B3");
+ displayableNames.put("iso-b4", "B4");
+ displayableNames.put("iso-b5", "B5");
+ displayableNames.put("iso-b6", "B6");
+ displayableNames.put("iso-b7", "B7");
+ displayableNames.put("iso-b8", "B8");
+ displayableNames.put("iso-b9", "B9");
+ displayableNames.put("iso-b10", "B10");
+ displayableNames.put("na-letter", "US Letter");
+ displayableNames.put("na-legal", "US Legal");
+ displayableNames.put("na-8x10", "US 8x10 inch");
+ displayableNames.put("na-5x7", "US 5x7 inch");
+ displayableNames.put("executive", "Executive");
+ displayableNames.put("folio", "Folio");
+ displayableNames.put("invoice", "Invoice");
+ displayableNames.put("tabloid", "Tabloid");
+ displayableNames.put("ledger", "Ledger");
+ displayableNames.put("quarto", "Quarto");
+ displayableNames.put("iso-c0", "C0");
+ displayableNames.put("iso-c1", "C1");
+ displayableNames.put("iso-c2", "C2");
+ displayableNames.put("iso-c3", "C3");
+ displayableNames.put("iso-c4", "C4");
+ displayableNames.put("iso-c5", "C5");
+ displayableNames.put("iso-c6", "C6");
+ displayableNames.put("iso-designated-long", "ISO Designated Long Size");
+ displayableNames.put("jis-b0", "Japanese B0");
+ displayableNames.put("jis-b1", "Japanese B1");
+ displayableNames.put("jis-b2", "Japanese B2");
+ displayableNames.put("jis-b3", "Japanese B3");
+ displayableNames.put("jis-b4", "Japanese B4");
+ displayableNames.put("jis-b5", "Japanese B5");
+ displayableNames.put("jis-b6", "Japanese B6");
+ displayableNames.put("jis-b7", "Japanese B7");
+ displayableNames.put("jis-b8", "Japanese B8");
+ displayableNames.put("jis-b9", "Japanese B9");
+ displayableNames.put("jis-b10", "Japanese B10");
+ displayableNames.put("a", "US Letter");
+ displayableNames.put("b", "Engineering ANSI B");
+ displayableNames.put("c", "Engineering ANSI C");
+ displayableNames.put("d", "Engineering ANSI D");
+ displayableNames.put("e", "Engineering ANSI E");
+ displayableNames.put("arch-a", "Architectural A");
+ displayableNames.put("arch-b", "Architectural B");
+ displayableNames.put("arch-c", "Architectural C");
+ displayableNames.put("arch-d", "Architectural D");
+ displayableNames.put("arch-e", "Architectural E");
+ displayableNames.put("japanese-postcard", "Japanese Postcard");
+ displayableNames.put("oufuko-postcard", "Oufuko Postcard");
+ displayableNames.put("italian-envelope", "Italian Envelope");
+ displayableNames.put("personal-envelope", "Personal Envelope");
+ displayableNames.put("na-number-11-envelope", "#11 Envelope");
+ displayableNames.put("na-number-12-envelope", "#12 Envelope");
+ displayableNames.put("na-number-14-envelope", "#14 Envelope");
+ displayableNames.put("na-10x13-envelope", "10\"x13\" Envelope");
+ displayableNames.put("na-9x12-envelope", "9\"x12\" Envelope");
+ displayableNames.put("na-number-10-envelope", "#10 Envelope");
+ displayableNames.put("na-7x9-envelope", "7\"x9\" Envelope");
+ displayableNames.put("na-9x11-envelope", "9\"x11\" Envelope");
+ displayableNames.put("na-10x14-envelope", "10\"x14\" Envelope");
+ displayableNames.put("na-number-9-envelope", "#9 Envelope");
+ displayableNames.put("na-6x9-envelope", "6\"x9\" Envelope");
+ displayableNames.put("na-10x15-envelope", "10\"x15\" Envelope");
+ displayableNames.put("monarch-envelope", "Monarch Envelope");
+ }
+
}
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.print.visitor.FinSetVisitorStrategy;
import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
import javax.print.attribute.standard.MediaSizeName;
+import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Set;
switch (printableContext.getPrintable()) {
case DESIGN_REPORT:
DesignReport dp = new DesignReport(doc, idoc);
- dp.print(writer);
+ dp.writeToDocument(writer);
idoc.newPage();
break;
case FIN_TEMPLATE:
- final ComponentVisitor finVisitor = new ComponentVisitor(new FinSetVisitorStrategy(idoc,
- writer,
- stages));
- finVisitor.visit(doc.getRocket());
- finVisitor.close();
+ final FinSetVisitorStrategy finWriter = new FinSetVisitorStrategy(idoc,
+ writer,
+ stages);
+ finWriter.writeToDocument(doc.getRocket());
break;
case PARTS_DETAIL:
- final ComponentVisitor detailVisitor = new ComponentVisitor(new PartsDetailVisitorStrategy(idoc,
- writer,
- stages));
- detailVisitor.visit(doc.getRocket());
+ final PartsDetailVisitorStrategy detailVisitor = new PartsDetailVisitorStrategy(idoc,
+ writer,
+ stages);
+ detailVisitor.writeToDocument(doc.getRocket());
detailVisitor.close();
idoc.newPage();
break;
- /* case PARTS_LIST:
- final ComponentVisitor partsVisitor = new ComponentVisitor(new PartsListVisitorStrategy(idoc,
- writer,
- stages));
- partsVisitor.visit(doc.getRocket());
- partsVisitor.close();
- idoc.newPage();
- break;
- */
}
}
//Stupid iText throws a really nasty exception if there is no data when close is called.
}
catch (ExceptionConverter ec) {
}
-/* finally {
- if (fileOutputStream != null) {
+ finally {
+ if (outputFile != null) {
try {
- fileOutputStream.close();
+ outputFile.close();
}
catch (IOException e) {
}
}
}
- */
+
}
private Rectangle convertWithDefault (final MediaSizeName msn) {
--- /dev/null
+/*\r
+ * PrintServiceDialog.java\r
+ *\r
+ */\r
+package net.sf.openrocket.gui.print;\r
+\r
+import net.miginfocom.swing.MigLayout;\r
+\r
+import javax.print.DocFlavor;\r
+import javax.print.PrintService;\r
+import javax.print.ServiceUIFactory;\r
+import javax.print.attribute.HashPrintRequestAttributeSet;\r
+import javax.print.attribute.PrintRequestAttribute;\r
+import javax.print.attribute.PrintRequestAttributeSet;\r
+import javax.print.attribute.PrintServiceAttribute;\r
+import javax.print.attribute.standard.*;\r
+import javax.swing.*;\r
+import javax.swing.border.EmptyBorder;\r
+import javax.swing.event.PopupMenuEvent;\r
+import javax.swing.event.PopupMenuListener;\r
+import java.awt.*;\r
+import java.awt.event.*;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
+\r
+public class PrintServiceDialog extends JDialog implements ActionListener {\r
+\r
+ public static final int APPROVE = 1;\r
+ private JButton btnCancel, btnPrint;\r
+ private boolean pdfFlavorSupported = true;\r
+ private PrintService services[];\r
+ private int defaultServiceIndex, status;\r
+ private PrintRequestAttributeSet asOriginal;\r
+ private HashPrintRequestAttributeSet asCurrent;\r
+ private PrintService psCurrent;\r
+ private DocFlavor docFlavor;\r
+ private GeneralPanel pnlGeneral;\r
+ private static final String GENERAL_TAB_TITLE = "General";\r
+ private static final String PRINT_BUTTON_LABEL = "Print";\r
+ private static final String CANCEL_BUTTON_LABEL = "Cancel";\r
+\r
+ private class MediaPanel extends JPanel\r
+ implements ItemListener {\r
+\r
+ private static final String strTitle = "Media";\r
+ private static final String SOURCE = "Source:";\r
+ private static final String SIZE = "Size:";\r
+\r
+ private JLabel lblSize, lblSource;\r
+ private JComboBox cbSize, cbSource;\r
+ private ArrayList<MediaSizeName> sizes;\r
+ private ArrayList sources;\r
+\r
+ private String getMediaName (String s) {\r
+ String s1 = s.replace(' ', '-');\r
+ s1 = s1.replace('#', 'n');\r
+ return PaperSize.toDisplayable(s1);\r
+ }\r
+\r
+ public void itemStateChanged (ItemEvent itemevent) {\r
+ Object obj = itemevent.getSource();\r
+ if (itemevent.getStateChange() == 1) {\r
+ if (obj == cbSize) {\r
+ int i = cbSize.getSelectedIndex();\r
+ if (i >= 0 && i < sizes.size()) {\r
+ if (cbSource.getItemCount() > 1 && cbSource.getSelectedIndex() >= 1) {\r
+ int k = cbSource.getSelectedIndex() - 1;\r
+ MediaTray mediatray = (MediaTray) sources.get(k);\r
+ asCurrent.add(new MediaWrapper(mediatray));\r
+ }\r
+ asCurrent.add(sizes.get(i));\r
+ }\r
+ }\r
+ else if (obj == cbSource) {\r
+ int j = cbSource.getSelectedIndex();\r
+ if (j >= 1 && j < sources.size() + 1) {\r
+ asCurrent.remove(MediaWrapper.class);\r
+ asCurrent.add((MediaTray) sources.get(j - 1));\r
+ }\r
+ else if (j == 0) {\r
+ asCurrent.remove(MediaWrapper.class);\r
+ if (cbSize.getItemCount() > 0) {\r
+ int l = cbSize.getSelectedIndex();\r
+ asCurrent.add(sizes.get(l));\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ public void updateInfo () {\r
+ boolean flag = false;\r
+ cbSize.removeItemListener(this);\r
+ cbSize.removeAllItems();\r
+ cbSource.removeItemListener(this);\r
+ cbSource.removeAllItems();\r
+ cbSource.addItem(getMediaName("auto-select"));\r
+ sizes.clear();\r
+ sources.clear();\r
+ if (psCurrent.isAttributeCategorySupported(Media.class)) {\r
+ flag = true;\r
+ Object obj = null;\r
+ try {\r
+ obj = psCurrent.getSupportedAttributeValues(Media.class, docFlavor, asCurrent);\r
+ }\r
+ catch (IllegalArgumentException iae) {\r
+ pdfFlavorSupported = false;\r
+ //dgp\r
+ }\r
+ if (obj instanceof Media[]) {\r
+ Media amedia[] = (Media[]) obj;\r
+ Set<SortableMediaSizeName> sizeSet = new TreeSet<SortableMediaSizeName>();\r
+\r
+ for (int i = 0; i < amedia.length; i++) {\r
+ Media media = amedia[i];\r
+ if (media instanceof MediaSizeName) {\r
+ sizeSet.add(new SortableMediaSizeName((MediaSizeName) media));\r
+ }\r
+ else if (media instanceof MediaTray) {\r
+ sources.add(media);\r
+ cbSource.addItem(getMediaName(media.toString()));\r
+ }\r
+ }\r
+\r
+ //The set eliminates duplicates.\r
+ for (Iterator<SortableMediaSizeName> mediaSizeNameIterator = sizeSet.iterator(); mediaSizeNameIterator\r
+ .hasNext();) {\r
+ SortableMediaSizeName media = mediaSizeNameIterator.next();\r
+\r
+ sizes.add(media.getMediaSizeName());\r
+ cbSize.addItem(media.toString());\r
+ }\r
+\r
+ }\r
+ }\r
+ boolean flag1 = flag && sizes.size() > 0;\r
+ lblSize.setEnabled(flag1);\r
+ cbSize.setEnabled(flag1);\r
+ cbSource.setEnabled(false);\r
+ lblSource.setEnabled(false);\r
+ if (flag) {\r
+ Media media = (Media) asCurrent.get(Media.class);\r
+ boolean attributeValueSupported = false;\r
+ try {\r
+ attributeValueSupported = psCurrent.isAttributeValueSupported(media, docFlavor, asCurrent);\r
+ }\r
+ catch (IllegalArgumentException iae) {\r
+ pdfFlavorSupported = false;\r
+ //dgp\r
+ }\r
+ if (media == null || !attributeValueSupported) {\r
+ media = (Media) psCurrent.getDefaultAttributeValue(Media.class);\r
+ if (media == null && sizes.size() > 0) {\r
+ media = sizes.get(0);\r
+ }\r
+ if (media != null) {\r
+ asCurrent.add(media);\r
+ }\r
+ }\r
+ if (media != null) {\r
+ if (media instanceof MediaSizeName) {\r
+ MediaSizeName mediasizename = (MediaSizeName) media;\r
+ cbSize.setSelectedIndex(sizes.indexOf(mediasizename));\r
+ }\r
+ else if (media instanceof MediaTray) {\r
+ MediaTray mediatray = (MediaTray) media;\r
+ cbSource.setSelectedIndex(sources.indexOf(mediatray) + 1);\r
+ }\r
+ }\r
+ else {\r
+ cbSize.setSelectedIndex(sizes.size() <= 0 ? -1 : 0);\r
+ cbSource.setSelectedIndex(0);\r
+ }\r
+ int j = cbSize.getSelectedIndex();\r
+ if (j >= 0 && j < sizes.size()) {\r
+ asCurrent.add(sizes.get(j));\r
+ }\r
+ j = cbSource.getSelectedIndex();\r
+ if (j >= 1 && j < sources.size() + 1) {\r
+ asCurrent.add((MediaTray) sources.get(j - 1));\r
+ }\r
+ }\r
+ cbSize.addItemListener(this);\r
+ cbSource.addItemListener(this);\r
+ }\r
+\r
+ public MediaPanel () {\r
+ super(new MigLayout("fill, gap rel unrel"));\r
+ sizes = new ArrayList<MediaSizeName>();\r
+ sources = new ArrayList();\r
+ setBorder(BorderFactory.createTitledBorder(strTitle));\r
+ cbSize = new JComboBox();\r
+ cbSource = new JComboBox();\r
+ lblSize = new JLabel(SIZE, 11);\r
+ lblSize.setDisplayedMnemonic(PrintServiceDialog.getMnemonic(SIZE));\r
+ lblSize.setLabelFor(cbSize);\r
+ add(lblSize);\r
+ add(cbSize, "wrap");\r
+ lblSource = new JLabel(SOURCE, 11);\r
+ lblSource.setDisplayedMnemonic(PrintServiceDialog.getMnemonic(SOURCE));\r
+ lblSource.setLabelFor(cbSource);\r
+ add(lblSource);\r
+ add(cbSource);\r
+ }\r
+\r
+ class SortableMediaSizeName implements Comparable {\r
+ MediaSizeName delegate;\r
+\r
+ String displayableName;\r
+\r
+ SortableMediaSizeName(MediaSizeName msn) {\r
+ delegate = msn;\r
+ displayableName = getMediaName(delegate.toString());\r
+ if (displayableName == null) {\r
+ displayableName = delegate.toString();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns a string value corresponding to this enumeration value.\r
+ */\r
+ @Override\r
+ public String toString () {\r
+ return displayableName;\r
+ }\r
+\r
+ @Override\r
+ public int compareTo (final Object o) {\r
+ String name = displayableName;\r
+ if (name != null) {\r
+ return name.compareTo(o.toString());\r
+ }\r
+ return 1;\r
+ }\r
+\r
+ @Override\r
+ public boolean equals (final Object o) {\r
+ if (this == o) {\r
+ return true;\r
+ }\r
+ if (o == null || getClass() != o.getClass()) {\r
+ return false;\r
+ }\r
+\r
+ final SortableMediaSizeName that = (SortableMediaSizeName) o;\r
+\r
+ return displayableName.equals(that.displayableName);\r
+ }\r
+\r
+ @Override\r
+ public int hashCode () {\r
+ return displayableName.hashCode();\r
+ }\r
+\r
+ MediaSizeName getMediaSizeName() {\r
+ return delegate;\r
+ }\r
+ }\r
+ }\r
+\r
+ private class PrintServicePanel extends JPanel\r
+ implements ActionListener, ItemListener, PopupMenuListener {\r
+\r
+ private final String strTitle = "Print Service";\r
+ private JButton btnProperties;\r
+ private JComboBox cbName;\r
+ private JLabel lblType, lblStatus, lblInfo;\r
+ private JLabel vType, vStatus, vInfo;\r
+ private ServiceUIFactory uiFactory;\r
+ private boolean changedService;\r
+\r
+ public PrintServicePanel () {\r
+ super(new MigLayout("fill, gap rel unrel"));\r
+ changedService = false;\r
+ uiFactory = psCurrent.getServiceUIFactory();\r
+ setBorder(BorderFactory.createTitledBorder(strTitle));\r
+ String as[] = new String[services.length];\r
+ for (int i = 0; i < as.length; i++) {\r
+ as[i] = services[i].getName();\r
+ }\r
+\r
+ cbName = new JComboBox(as);\r
+ cbName.setSelectedIndex(defaultServiceIndex);\r
+ cbName.addItemListener(this);\r
+ cbName.addPopupMenuListener(this);\r
+ JLabel jlabel = new JLabel(("Name:"), 11);\r
+ jlabel.setDisplayedMnemonic(PrintServiceDialog.getMnemonic("Name"));\r
+ jlabel.setLabelFor(cbName);\r
+ add(jlabel);\r
+ add(cbName);\r
+ btnProperties = PrintServiceDialog.createButton("Properties...", this);\r
+ add(btnProperties, "wrap");\r
+ lblStatus = new JLabel("Status:", 11);\r
+ add(lblStatus);\r
+ vStatus = new JLabel();\r
+ add(vStatus, "wrap");\r
+ lblType = new JLabel("Type:", 11);\r
+ vType = new JLabel();\r
+ add(lblType);\r
+ add(vType, "wrap");\r
+ lblInfo = new JLabel("Info:", 11);\r
+ vInfo = new JLabel();\r
+ add(lblInfo);\r
+ add(vInfo, "wrap");\r
+ }\r
+\r
+ public void actionPerformed (ActionEvent actionevent) {\r
+ Object obj = actionevent.getSource();\r
+ if (obj == btnProperties && uiFactory != null) {\r
+ JDialog jdialog = (JDialog) uiFactory.getUI(3, "javax.swing.JDialog");\r
+ if (jdialog != null) {\r
+ jdialog.show();\r
+ }\r
+ else {\r
+ btnProperties.setEnabled(false);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void itemStateChanged (ItemEvent itemevent) {\r
+ if (itemevent.getStateChange() == 1) {\r
+ int i = cbName.getSelectedIndex();\r
+ if (i >= 0 && i < services.length && !services[i].equals(psCurrent)) {\r
+ psCurrent = services[i];\r
+ uiFactory = psCurrent.getServiceUIFactory();\r
+ changedService = true;\r
+ Destination destination = (Destination) asOriginal.get(\r
+ Destination.class);\r
+ if ((destination != null) && psCurrent.isAttributeCategorySupported(\r
+ Destination.class)) {\r
+ asCurrent.add(destination);\r
+ }\r
+ else {\r
+ asCurrent.remove(Destination.class);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ public void popupMenuWillBecomeVisible (PopupMenuEvent popupmenuevent) {\r
+ changedService = false;\r
+ }\r
+\r
+ public void popupMenuWillBecomeInvisible (PopupMenuEvent popupmenuevent) {\r
+ if (changedService) {\r
+ changedService = false;\r
+ updatePanels();\r
+ }\r
+ }\r
+\r
+ public void popupMenuCanceled (PopupMenuEvent popupmenuevent) {\r
+ }\r
+\r
+ public void updateInfo () {\r
+ PrintServiceAttribute psa = psCurrent.getAttribute(PrinterMakeAndModel.class);\r
+ if (psa != null) {\r
+ vType.setText(psa.toString());\r
+ }\r
+ psa = psCurrent.getAttribute(PrinterIsAcceptingJobs.class);\r
+ if (psa != null) {\r
+ vStatus.setText((psa.toString()));\r
+ }\r
+ psa = psCurrent.getAttribute(PrinterInfo.class);\r
+ if (psa != null) {\r
+ vInfo.setText(psa.toString());\r
+ }\r
+ btnProperties.setEnabled(uiFactory != null);\r
+ }\r
+\r
+ }\r
+\r
+ private class GeneralPanel extends JPanel {\r
+\r
+ public void updateInfo () {\r
+ pnlPrintService.updateInfo();\r
+ pnlMedia.updateInfo();\r
+ }\r
+\r
+ private PrintServicePanel pnlPrintService;\r
+ private MediaPanel pnlMedia;\r
+\r
+ public GeneralPanel () {\r
+ super(new MigLayout("fill, gap rel unrel"));\r
+ pnlPrintService = new PrintServicePanel();\r
+ add(pnlPrintService, "wrap");\r
+ pnlMedia = new MediaPanel();\r
+ add(pnlMedia, "wrap");\r
+ }\r
+ }\r
+\r
+\r
+ public PrintServiceDialog (GraphicsConfiguration graphicsconfiguration, int i, int j, PrintService aprintservice[],\r
+ int k, DocFlavor docflavor, PrintRequestAttributeSet printrequestattributeset,\r
+ Dialog dialog, JPanel... additional) {\r
+ super(dialog, PRINT_BUTTON_LABEL, true, graphicsconfiguration);\r
+ initPrintDialog(i, j, aprintservice, k, docflavor, printrequestattributeset, additional);\r
+ }\r
+\r
+ void initPrintDialog (int i, int j, PrintService aprintservice[], int k, DocFlavor docflavor,\r
+ PrintRequestAttributeSet printrequestattributeset,\r
+ JPanel... additional) {\r
+ services = aprintservice;\r
+ defaultServiceIndex = k;\r
+ asOriginal = printrequestattributeset;\r
+ asCurrent = new HashPrintRequestAttributeSet(printrequestattributeset);\r
+ psCurrent = aprintservice[k];\r
+ docFlavor = docflavor;\r
+ Container container = getContentPane();\r
+ container.setLayout(new BorderLayout());\r
+ final JTabbedPane tpTabs = new JTabbedPane();\r
+ tpTabs.setBorder(new EmptyBorder(5, 5, 5, 5));\r
+\r
+ if (additional != null) {\r
+ for (JPanel anAdditional : additional) {\r
+ tpTabs.add(anAdditional, anAdditional.getName(), 0);\r
+ }\r
+ }\r
+ pnlGeneral = new GeneralPanel();\r
+ tpTabs.add(GENERAL_TAB_TITLE, pnlGeneral);\r
+\r
+ container.add(tpTabs, "Center");\r
+ updatePanels();\r
+ JPanel jpanel = new JPanel(new MigLayout());\r
+ btnPrint = createExitButton(PRINT_BUTTON_LABEL, this);\r
+ jpanel.add(btnPrint, "x 300");\r
+ getRootPane().setDefaultButton(btnPrint);\r
+ btnPrint.setEnabled(pdfFlavorSupported);\r
+\r
+ btnCancel = createExitButton(CANCEL_BUTTON_LABEL, this);\r
+ handleEscKey(btnCancel);\r
+ jpanel.add(btnCancel, "x 380");\r
+ container.add(jpanel, "South");\r
+ addWindowListener(new WindowAdapter() {\r
+ public void windowClosing (WindowEvent windowevent) {\r
+ dispose(2);\r
+ }\r
+ }\r
+ );\r
+ setResizable(false);\r
+ setLocation(i, j);\r
+ pack();\r
+ }\r
+\r
+ private void handleEscKey (JButton jbutton) {\r
+ AbstractAction abstractaction = new AbstractAction() {\r
+\r
+ public void actionPerformed (ActionEvent actionevent) {\r
+ dispose(2);\r
+ }\r
+\r
+ };\r
+ KeyStroke keystroke = KeyStroke.getKeyStroke('\033', false);\r
+ InputMap inputmap = jbutton.getInputMap(2);\r
+ ActionMap actionmap = jbutton.getActionMap();\r
+ if (inputmap != null && actionmap != null) {\r
+ inputmap.put(keystroke, "cancel");\r
+ actionmap.put("cancel", abstractaction);\r
+ }\r
+ }\r
+\r
+ public int getStatus () {\r
+ return status;\r
+ }\r
+\r
+ public PrintRequestAttributeSet getAttributes () {\r
+ if (status == 1) {\r
+ return asCurrent;\r
+ }\r
+ else {\r
+ return asOriginal;\r
+ }\r
+ }\r
+\r
+ public PrintService getPrintService () {\r
+ if (status == 1) {\r
+ return psCurrent;\r
+ }\r
+ else {\r
+ return null;\r
+ }\r
+ }\r
+\r
+ public void dispose (int i) {\r
+ status = i;\r
+ super.dispose();\r
+ }\r
+\r
+ public void actionPerformed (ActionEvent actionevent) {\r
+ Object obj = actionevent.getSource();\r
+ boolean flag = false;\r
+ if (obj == btnPrint) {\r
+ flag = true;\r
+ if (pnlGeneral != null) {\r
+ asCurrent.remove(Destination.class);\r
+ }\r
+ }\r
+ dispose(flag ? 1 : 2);\r
+ }\r
+\r
+\r
+ private void updatePanels () {\r
+ pnlGeneral.updateInfo();\r
+ }\r
+\r
+ private static char getMnemonic (String s) {\r
+ if (s != null && s.length() > 0) {\r
+ return s.charAt(0);\r
+ }\r
+ else {\r
+ return '\0';\r
+ }\r
+ }\r
+\r
+ private static JButton createButton (String s, ActionListener actionlistener) {\r
+ JButton jbutton = new JButton(s);\r
+ jbutton.setMnemonic(getMnemonic(s));\r
+ jbutton.addActionListener(actionlistener);\r
+ return jbutton;\r
+ }\r
+\r
+ private static JButton createExitButton (String s, ActionListener actionlistener) {\r
+ JButton jbutton = new JButton(s);\r
+ jbutton.addActionListener(actionlistener);\r
+ jbutton.getAccessibleContext().setAccessibleDescription(s);\r
+ return jbutton;\r
+ }\r
+\r
+ static class MediaWrapper\r
+ implements PrintRequestAttribute {\r
+\r
+ private Media media;\r
+\r
+ MediaWrapper (Media theMedia) {\r
+ media = theMedia;\r
+ }\r
+\r
+ Media getMedia () {\r
+ return media;\r
+ }\r
+\r
+ public final Class getCategory () {\r
+ return this.getClass();\r
+ }\r
+\r
+ public final String getName () {\r
+ return "mw";\r
+ }\r
+\r
+ public String toString () {\r
+ return media.toString();\r
+ }\r
+\r
+ public int hashCode () {\r
+ return media.hashCode();\r
+ }\r
+\r
+ }\r
+\r
+}\r
package net.sf.openrocket.gui.print;
-import com.itextpdf.text.Chunk;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.*;
import com.itextpdf.text.Font;
-import com.itextpdf.text.Paragraph;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
import javax.print.DocFlavor;
import javax.print.PrintService;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaSize;
-import javax.swing.RepaintManager;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
+import javax.swing.*;
+import java.awt.*;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.util.Locale;
+/**
+ * Utilities methods and fonts used for printing.
+ */
public class PrintUtilities implements Printable {
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
+
public static final int NORMAL_FONT_SIZE = Font.DEFAULTSIZE - 3;
public static final int SMALL_FONT_SIZE = NORMAL_FONT_SIZE - 3;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
}
+ /**
+ * Add text as a new paragraph in a given font to the document.
+ *
+ * @param document the document
+ * @param font the font
+ * @param title the title
+ */
public static void addText (Document document, com.itextpdf.text.Font font, String title) {
Chunk sectionHeader = new Chunk(title);
sectionHeader.setFont(font);
document.add(p);
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not add paragraph.", e);
}
}
}
*/
package net.sf.openrocket.gui.print;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
/**
* Instances of this class are meant to keep track of what the user has selected to be printed.
*/
private OpenRocketPrintable printable;
+ /**
+ * Sort of a reverse map that tracks each type of printable item and the stages for which that item is to be printed.
+ */
private final Map<OpenRocketPrintable, Set<Integer>> previous = new TreeMap<OpenRocketPrintable, Set<Integer>>();
-
+ /**
+ * Constructor.
+ */
public PrintableContext () {
}
printable = thePrintable;
}
- public void add (final Integer theStageNumber, final OpenRocketPrintable thePrintable)
- throws IllegalArgumentException {
+ /**
+ * Add a type of printable to a stage (number).
+ *
+ * @param theStageNumber the stage number
+ * @param thePrintable the printable to associate with the stage
+ */
+ public void add (final Integer theStageNumber, final OpenRocketPrintable thePrintable) {
Set<Integer> stages = previous.get(thePrintable);
if (stages == null) {
stages = new TreeSet<Integer>();
}
}
-
+ /** PrintableContext iterator. */
public Iterator<PrintableContext> iterator () {
return new Iterator<PrintableContext>() {
+++ /dev/null
-/*
- * BaseVisitorStrategy.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-import com.itextpdf.text.Document;
-import com.itextpdf.text.pdf.PdfWriter;
-
-/**
- * This abstract class contains boilerplate functionality to support visiting the components of a rocket. It is a
- * visitor strategy, not a visitor per se.
- */
-public abstract class BaseVisitorStrategy implements ComponentVisitorStrategy {
-
- /**
- * The owning visitor.
- */
- protected ComponentVisitor parent;
-
- /**
- * The iText document.
- */
- protected Document document;
-
- /**
- * The direct iText writer.
- */
- protected PdfWriter writer;
-
- /**
- * The stages selected.
- */
- protected Set<Integer> stages;
-
- /**
- * State variable to track the level of hierarchy.
- */
- protected int level = 0;
-
- /**
- * Default no-arg constructor.
- */
- public BaseVisitorStrategy() {
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- */
- public BaseVisitorStrategy(Document doc) {
- this(doc, null);
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param theWriter an iText byte writer
- */
- public BaseVisitorStrategy(Document doc, PdfWriter theWriter) {
- this(doc, theWriter, new HashSet<Integer>());
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param theWriter an iText byte writer
- * @param theStages a set of stage numbers
- */
- public BaseVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStages) {
- document = doc;
- writer = theWriter;
- stages = theStages;
- }
-
- /**
- * 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;
- }
-
- /**
- * Recurse through the given rocket component.
- *
- * @param root the root component; all children will be visited recursively
- */
- protected void goDeep(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) {
- level++;
- for (RocketComponent rocketComponent : theRc) {
- rocketComponent.accept(parent);
- }
- level--;
- }
-
- /**
- * Get the dimensions of the paper page.
- *
- * @return an internal Dimension
- */
- protected Dimension getPageSize() {
- return new Dimension(document.getPageSize().getWidth(),
- document.getPageSize().getHeight());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Rocket visitable) {
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RocketComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- }
-}
-
-class Dimension {
- public float width;
- public float height;
-
- public Dimension(float w, float h) {
- width = w;
- height = h;
- }
-
- public float getWidth() {
- return width;
- }
-
- public float getHeight() {
- return height;
- }
-}
\ No newline at end of file
import com.itextpdf.text.pdf.PdfWriter;
import net.sf.openrocket.gui.print.ITextHelper;
import net.sf.openrocket.gui.print.PrintableFinSet;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
-import java.awt.Graphics2D;
+import java.awt.*;
import java.awt.image.BufferedImage;
+import java.util.List;
import java.util.Set;
/**
- * A visitor strategy for drawing fin templates.
+ * A strategy for drawing fin templates.
*/
-public class FinSetVisitorStrategy extends BaseVisitorStrategy {
+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 theStagesToVisit The stages to be visited by this strategy
*/
public FinSetVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
}
/**
- * {@inheritDoc}
+ * Recurse through the given rocket component.
+ *
+ * @param root the root component; all children will be visited recursively
*/
- @Override
- public void visit (final TrapezoidFinSet visitable) {
- doVisit(visitable);
+ public void writeToDocument (final RocketComponent root) {
+ List<RocketComponent> rc = root.getChildren();
+ goDeep(rc);
}
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final EllipticalFinSet visitable) {
- doVisit(visitable);
- }
/**
- * {@inheritDoc}
+ * Recurse through the given rocket component.
+ *
+ * @param theRc an array of rocket components; all children will be visited recursively
*/
- @Override
- public void visit (final FreeformFinSet visitable) {
- doVisit(visitable);
+ 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());
+ }
+ }
}
/**
}
}
catch (DocumentException e) {
- e.printStackTrace();
+ 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.
*
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
-/*
- * MotorMountVisitor.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import com.itextpdf.text.Document;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A visitor strategy for finding data about motor configurations. This visitor accumulates information about the
- * motors currently 'installed' into each motor mount in the rocket. When the visitor is complete, invoke {@link
- * #getMotors()} to obtain the list of Motor instances that correspond to the motor configuration.
- */
-public class MotorMountVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * The motor configuration identifier.
- */
- private String mid;
-
- /** The accumulating list of motors. */
- private List<Motor> motors = new ArrayList<Motor>();
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param motorConfigID the motor configuration ID
- */
- public MotorMountVisitorStrategy (Document doc,
- String motorConfigID) {
- super(doc);
- mid = motorConfigID;
- }
-
- /**
- * Override the method that determines if the visiting should be going deep.
- *
- * @param stageNumber a stage number
- *
- * @return true, always
- */
- public boolean shouldVisitStage (int stageNumber) {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final BodyTube visitable) {
- if (visitable.isMotorMount()) {
- doVisit(visitable);
- }
- else {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final InnerTube visitable) {
- if (visitable.isMotorMount()) {
- doVisit(visitable);
- }
- }
-
- /**
- * 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 MotorMount visitable) {
- final Motor motor = visitable.getMotor(mid);
- if (motor != null) {
- motors.add(motor);
- }
- }
-
- /**
- * Answer with the list of motors that have been accumulated from visiting all of the motor mount components in the
- * rocket component hierarchy.
- *
- * @return a list of motors
- */
- public List<Motor> getMotors () {
- return motors;
- }
-
-}
-
-
*/
package net.sf.openrocket.gui.print.visitor;
-import java.io.IOException;
-import java.text.NumberFormat;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import javax.swing.ImageIcon;
-
+import com.itextpdf.text.*;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
import net.sf.openrocket.gui.main.ComponentIcons;
import net.sf.openrocket.gui.print.ITextHelper;
import net.sf.openrocket.gui.print.PrintUtilities;
import net.sf.openrocket.gui.print.PrintableFinSet;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Coaxial;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.*;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
-import com.itextpdf.text.BadElementException;
-import com.itextpdf.text.Chunk;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Font;
-import com.itextpdf.text.Image;
-import com.itextpdf.text.Phrase;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.PdfPCell;
-import com.itextpdf.text.pdf.PdfPTable;
-import com.itextpdf.text.pdf.PdfWriter;
+import javax.swing.*;
+import java.text.NumberFormat;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
/**
* A visitor strategy for creating documentation about parts details.
*/
-public class PartsDetailVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * The number of columns in the table.
- */
- private static final int TABLE_COLUMNS = 7;
-
- /**
- * The parts detail is represented as an iText table.
- */
- PdfPTable grid;
-
- /**
- * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
- *
- * @param doc The iText document
- * @param theWriter The direct iText writer
- * @param theStagesToVisit The stages to be visited by this strategy
- */
- public PartsDetailVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
- PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, "Parts Detail");
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
- try {
- if (grid != null) {
- document.add(grid);
- }
- document.add(ITextHelper.createPhrase(visitable.getName()));
- grid = new PdfPTable(TABLE_COLUMNS);
- grid.setWidthPercentage(100);
- grid.setHorizontalAlignment(Element.ALIGN_LEFT);
- } catch (DocumentException e) {
- }
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
-
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell());
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- grid.addCell(visitable.getName());
- grid.completeRow();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- grid.addCell(iconToImage(visitable));
- final PdfPCell pCell = createNameCell(visitable.getName(), true);
- grid.addCell(pCell);
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
-
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
-
- Chunk fore = new Chunk("Fore Dia: " + appendLength(visitable.getForeRadius() * 2));
- fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- Chunk aft = new Chunk("Aft Dia: " + appendLength(visitable.getAftRadius() * 2));
- aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- final PdfPCell cell = ITextHelper.createCell();
- cell.addElement(fore);
- cell.addElement(aft);
- grid.addCell(cell);
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- PdfPCell cell = ITextHelper.createCell();
- cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- cell.setPaddingBottom(12f);
-
- grid.addCell(iconToImage(visitable));
- final PdfPCell nameCell = createNameCell(visitable.getName(), true);
- nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- nameCell.setPaddingBottom(12f);
- grid.addCell(nameCell);
- grid.addCell(cell);
- grid.addCell(cell);
- grid.addCell(cell);
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell(visitable.getType().getName(), PdfPCell.BOTTOM));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- try {
- if (grid != null) {
- document.add(grid);
- }
- } catch (DocumentException e) {
- e.printStackTrace();
- }
- }
-
- private PdfPCell createOuterDiaCell(final Coaxial visitable) {
- PdfPCell result = new PdfPCell();
- Phrase p = new Phrase();
- p.setLeading(12f);
- result.setVerticalAlignment(Element.ALIGN_TOP);
- result.setBorder(Rectangle.BOTTOM);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append("Dia");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- c.append("out");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(" " + appendLength(visitable.getOuterRadius() * 2));
- p.add(c);
- createInnerDiaCell(visitable, result);
- result.addElement(p);
- return result;
- }
-
- private void createInnerDiaCell(final Coaxial visitable, PdfPCell cell) {
- Phrase p = new Phrase();
- p.setLeading(14f);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append("Dia");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- c.append("in ");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(" " + appendLength(visitable.getInnerRadius() * 2));
- p.add(c);
- cell.addElement(p);
- }
-
- private void visitFins(FinSet visitable) {
-
- Image img = null;
- java.awt.Image awtImage = new PrintableFinSet(visitable).createImage();
-
- Collection<Coordinate> x = visitable.getComponentBounds();
-
- try {
- img = Image.getInstance(writer, awtImage, 0.25f);
- } catch (BadElementException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName() + " (" + visitable.getFinCount() + ")", true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell("Thick: " + appendLength(visitable.getThickness()), PdfPCell.BOTTOM));
- final PdfPCell pCell = new PdfPCell();
- pCell.setBorder(Rectangle.BOTTOM);
- pCell.addElement(img);
-
- grid.addCell(ITextHelper.createCell());
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- protected PdfPCell createLengthCell(double length) {
- return ITextHelper.createCell("Len: " + appendLength(length), PdfPCell.BOTTOM);
- }
-
- protected PdfPCell createMassCell(double mass) {
- return ITextHelper.createCell("Mass: " + appendMass(mass), PdfPCell.BOTTOM);
- }
-
- protected PdfPCell createNameCell(String v, boolean withIndent) {
- PdfPCell result = new PdfPCell();
- result.setBorder(Rectangle.BOTTOM);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- if (withIndent) {
- for (int x = 0; x < (level - 2) * 10; x++) {
- c.append(" ");
- }
- }
- c.append(v);
- result.setColspan(2);
- result.addElement(c);
- return result;
- }
-
- protected PdfPCell createMaterialCell(Material material) {
- PdfPCell cell = ITextHelper.createCell();
- cell.setLeading(13f, 0);
-
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(appendMaterial(material));
- cell.addElement(c);
- Chunk density = new Chunk();
- density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- density.append(appendMaterialDensity(material));
- cell.addElement(density);
- return cell;
- }
-
- protected PdfPCell iconToImage(final RocketComponent visitable) {
- final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
- try {
- Image im = Image.getInstance(icon.getImage(), null);
- im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
- PdfPCell cell = new PdfPCell(im);
- cell.setFixedHeight(icon.getIconHeight() * 0.6f);
- cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
- cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- cell.setBorder(PdfPCell.NO_BORDER);
- return cell;
- } catch (BadElementException e) {
- } catch (IOException e) {
- }
- return null;
- }
-
- protected String appendLength(double length) {
- final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
- return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
- }
-
- protected String appendMass(double mass) {
- final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
- return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
- }
-
- protected String appendMaterial(Material material) {
- return material.getName();
- }
-
- protected String appendMaterialDensity(Material material) {
- return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
- }
-
+public class PartsDetailVisitorStrategy {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
+
+ /**
+ * The number of columns in the table.
+ */
+ private static final int TABLE_COLUMNS = 7;
+
+ /**
+ * The parts detail is represented as an iText table.
+ */
+ PdfPTable grid;
+
+ /**
+ * The iText document.
+ */
+ protected Document document;
+
+ /**
+ * The direct iText writer.
+ */
+ protected PdfWriter writer;
+
+ /**
+ * The stages selected.
+ */
+ protected Set<Integer> stages;
+
+ /**
+ * State variable to track the level of hierarchy.
+ */
+ protected int level = 0;
+
+ private static final String LINES = "Lines: ";
+ private static final String MASS = "Mass: ";
+ private static final String LEN = "Len: ";
+ private static final String THICK = "Thick: ";
+ private static final String INNER = "in ";
+ private static final String DIAMETER = "Dia";
+ private static final String OUTER = "out";
+ private static final String WIDTH = "Width";
+ private static final String LENGTH = "Length";
+ private static final String SHROUD_LINES = "Shroud Lines";
+ private static final String AFT_DIAMETER = "Aft Dia: ";
+ private static final String FORE_DIAMETER = "Fore Dia: ";
+ private static final String PARTS_DETAIL = "Parts Detail";
+
+ /**
+ * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStagesToVisit The stages to be visited by this strategy
+ */
+ public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
+ PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
+ }
+
+ /**
+ * Print the parts detail.
+ *
+ * @param root the root component
+ */
+ public void writeToDocument (final RocketComponent root) {
+ goDeep(root.getChildren());
+ }
+
+ /**
+ * 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) {
+ level++;
+ for (RocketComponent rocketComponent : theRc) {
+ handle(rocketComponent);
+ }
+ level--;
+ }
+
+ /**
+ * Add a line to the detail report based upon the type of the component.
+ *
+ * @param component the component to print the detail for
+ */
+ private void handle (RocketComponent component) {
+ //This ugly if-then-else construct is not object oriented. Originally it was an elegant, and very OO savy, design
+ //using the Visitor pattern. Unfortunately, it was misunderstood and was removed.
+ if (component instanceof Stage) {
+ try {
+ if (grid != null) {
+ document.add(grid);
+ }
+ document.add(ITextHelper.createPhrase(component.getName()));
+ grid = new PdfPTable(TABLE_COLUMNS);
+ grid.setWidthPercentage(100);
+ grid.setHorizontalAlignment(Element.ALIGN_LEFT);
+ }
+ catch (DocumentException e) {
+ }
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof LaunchLug) {
+ LaunchLug ll = (LaunchLug) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+
+ grid.addCell(createMaterialCell(ll.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(ll));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof NoseCone) {
+ NoseCone nc = (NoseCone) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(nc.getMaterial()));
+ grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof Transition) {
+ Transition tran = (Transition) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(tran.getMaterial()));
+
+ Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
+ fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
+ aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ final PdfPCell cell = ITextHelper.createCell();
+ cell.addElement(fore);
+ cell.addElement(aft);
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof BodyTube) {
+ BodyTube bt = (BodyTube) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(bt.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(bt));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof FinSet) {
+ handleFins((FinSet) component);
+ }
+ else if (component instanceof BodyComponent) {
+ grid.addCell(component.getName());
+ grid.completeRow();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof ExternalComponent) {
+ ExternalComponent ext = (ExternalComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+
+ grid.addCell(createMaterialCell(ext.getMaterial()));
+ grid.addCell(ITextHelper.createCell());
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof InnerTube) {
+ InnerTube it = (InnerTube) component;
+ grid.addCell(iconToImage(component));
+ final PdfPCell pCell = createNameCell(component.getName(), true);
+ grid.addCell(pCell);
+ grid.addCell(createMaterialCell(it.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(it));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RadiusRingComponent) {
+ RadiusRingComponent rrc = (RadiusRingComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(rrc.getMaterial()));
+ if (component instanceof Bulkhead) {
+ grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
+ }
+ else {
+ grid.addCell(createOuterInnerDiaCell(rrc));
+ }
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RingComponent) {
+ RingComponent ring = (RingComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(ring));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof ShockCord) {
+ ShockCord ring = (ShockCord) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(ring.getCordLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof Parachute) {
+ Parachute chute = (Parachute) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(chute.getMaterial()));
+ grid.addCell(createDiaCell(chute.getDiameter()));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ grid.addCell(iconToImage(null));
+ grid.addCell(createNameCell(SHROUD_LINES, true));
+ grid.addCell(createMaterialCell(chute.getLineMaterial()));
+ grid.addCell(createLinesCell(chute.getLineCount()));
+ grid.addCell(createLengthCell(chute.getLineLength()));
+ grid.addCell(cell);
+ }
+ else if (component instanceof Streamer) {
+ Streamer ring = (Streamer) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(createStrip(ring));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof RecoveryDevice) {
+ RecoveryDevice device = (RecoveryDevice) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(device.getMaterial()));
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof MassObject) {
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+
+ grid.addCell(iconToImage(component));
+ final PdfPCell nameCell = createNameCell(component.getName(), true);
+ nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ nameCell.setPaddingBottom(12f);
+ grid.addCell(nameCell);
+ grid.addCell(cell);
+ grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
+ grid.addCell(cell);
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ }
+
+ /**
+ * Close the strategy by adding the last grid to the document.
+ */
+ public void close () {
+ try {
+ if (grid != null) {
+ document.add(grid);
+ }
+ }
+ catch (DocumentException e) {
+ log.error("Could not write last cell to document.", e);
+ }
+ }
+
+ /**
+ * Create a cell to document an outer 'diameter'. This is used for components that have no inner diameter, such as
+ * a solid parachute or bulkhead.
+ *
+ * @param diameter the diameter in default length units
+ *
+ * @return a formatted cell containing the diameter
+ */
+ private PdfPCell createDiaCell (final double diameter) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(OUTER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(diameter));
+ p.add(c);
+ result.addElement(p);
+ return result;
+ }
+
+ /**
+ * Create a PDF cell for a streamer.
+ *
+ * @param component a component that is a Coaxial
+ * @return the PDF cell that has the streamer documented
+ */
+ private PdfPCell createStrip (final Streamer component) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(LENGTH);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getStripLength()));
+ p.add(c);
+ result.addElement(p);
+
+ Phrase pw = new Phrase();
+ pw.setLeading(14f);
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(WIDTH);
+ pw.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getStripWidth()));
+ pw.add(c);
+ result.addElement(pw);
+
+ return result;
+ }
+
+ /**
+ * Create a PDF cell that documents both an outer and an inner diameter of a component.
+ *
+ * @param component a component that is a Coaxial
+ * @return the PDF cell that has the outer and inner diameters documented
+ */
+ private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(OUTER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getOuterRadius() * 2));
+ p.add(c);
+ createInnerDiaCell(component, result);
+ result.addElement(p);
+ return result;
+ }
+
+ /**
+ * Add inner diameter data to a cell.
+ *
+ * @param component a component that is a Coaxial
+ * @param cell the PDF cell to add the inner diameter data to
+ */
+ private void createInnerDiaCell (final Coaxial component, PdfPCell cell) {
+ Phrase p = new Phrase();
+ p.setLeading(14f);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(INNER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getInnerRadius() * 2));
+ p.add(c);
+ cell.addElement(p);
+ }
+
+ /**
+ * Add PDF cells for a fin set.
+ *
+ * @param theFinSet the fin set
+ */
+ private void handleFins (FinSet theFinSet) {
+
+ Image img = null;
+ java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
+
+ Collection<Coordinate> x = theFinSet.getComponentBounds();
+
+ try {
+ img = Image.getInstance(writer, awtImage, 0.25f);
+ }
+ catch (Exception e) {
+ log.error("Could not write image to document.", e);
+ }
+
+ grid.addCell(iconToImage(theFinSet));
+ grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
+ grid.addCell(createMaterialCell(theFinSet.getMaterial()));
+ grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
+ final PdfPCell pCell = new PdfPCell();
+ pCell.setBorder(Rectangle.BOTTOM);
+ pCell.addElement(img);
+
+ grid.addCell(ITextHelper.createCell());
+ grid.addCell(createMassCell(theFinSet.getMass()));
+
+ List<RocketComponent> rc = theFinSet.getChildren();
+ goDeep(rc);
+ }
+
+ /**
+ * Create a length formatted cell.
+ *
+ * @param length the length, in default length units
+ *
+ * @return a PdfPCell that is formatted with the length
+ */
+ protected PdfPCell createLengthCell (double length) {
+ return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a mass formatted cell.
+ *
+ * @param mass the mass, in default mass units
+ *
+ * @return a PdfPCell that is formatted with the mass
+ */
+ protected PdfPCell createMassCell (double mass) {
+ return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a (shroud) line count formatted cell.
+ *
+ * @param count the number of shroud lines
+ *
+ * @return a PdfPCell that is formatted with the line count
+ */
+ protected PdfPCell createLinesCell (int count) {
+ return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a cell formatted for a name (or any string for that matter).
+ *
+ * @param v the string to format into a PDF cell
+ * @param withIndent if true, then an indention is made scaled to the level of the part in the parent hierarchy
+ *
+ * @return a PdfPCell that is formatted with the string <code>v</code>
+ */
+ protected PdfPCell createNameCell (String v, boolean withIndent) {
+ PdfPCell result = new PdfPCell();
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ if (withIndent) {
+ for (int x = 0; x < (level - 2) * 10; x++) {
+ c.append(" ");
+ }
+ }
+ c.append(v);
+ result.setColspan(2);
+ result.addElement(c);
+ return result;
+ }
+
+ /**
+ * Create a cell that describes a material.
+ *
+ * @param material the material
+ *
+ * @return a PdfPCell that is formatted with a description of the material
+ */
+ protected PdfPCell createMaterialCell (Material material) {
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setLeading(13f, 0);
+
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(toMaterialName(material));
+ cell.addElement(c);
+ Chunk density = new Chunk();
+ density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ density.append(toMaterialDensity(material));
+ cell.addElement(density);
+ return cell;
+ }
+
+ /**
+ * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
+ *
+ * @param visitable the rocket component to create a cell with it's image
+ *
+ * @return a PdfPCell that is just an image that can be put into a PDF
+ */
+ protected PdfPCell iconToImage (final RocketComponent visitable) {
+ if (visitable != null) {
+ final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
+ try {
+ if (icon != null) {
+ Image im = Image.getInstance(icon.getImage(), null);
+ if (im != null) {
+ im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
+ PdfPCell cell = new PdfPCell(im);
+ cell.setFixedHeight(icon.getIconHeight() * 0.6f);
+ cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setBorder(PdfPCell.NO_BORDER);
+ return cell;
+ }
+ }
+ }
+ catch (Exception e) {
+ }
+ }
+ PdfPCell cell = new PdfPCell();
+ cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setBorder(PdfPCell.NO_BORDER);
+ return cell;
+ }
+
+ /**
+ * Format the length as a displayable string.
+ *
+ * @param length the length (assumed to be in default length units)
+ *
+ * @return a string representation of the length with unit abbreviation
+ */
+ protected String toLength (double length) {
+ final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
+ return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
+ }
+
+ /**
+ * Format the mass as a displayable string.
+ *
+ * @param mass the mass (assumed to be in default mass units)
+ *
+ * @return a string representation of the mass with mass abbreviation
+ */
+ protected String toMass (double mass) {
+ final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
+ return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
+ }
+
+ /**
+ * Get a displayable string of the material's name.
+ *
+ * @param material the material to output
+ *
+ * @return the material name
+ */
+ protected String toMaterialName (Material material) {
+ return material.getName();
+ }
+
+ /**
+ * Format the material density as a displayable string.
+ *
+ * @param material the material to output
+ *
+ * @return a string representation of the material density
+ */
+ protected String toMaterialDensity (Material material) {
+ return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
+ }
+
}
*/
package net.sf.openrocket.gui.print.visitor;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.PdfWriter;
+import net.sf.openrocket.rocketcomponent.*;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Coaxial;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-import com.itextpdf.text.Document;
-import com.itextpdf.text.pdf.PdfWriter;
-
/**
* A visitor strategy for creating documentation about a parts list.
*/
-public class PartsListVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * Accumulator for parts data.
- */
- private Map<PartsAccumulator, PartsAccumulator> crap = new HashMap<PartsAccumulator, PartsAccumulator>();
-
- /**
- * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
- *
- * @param doc The iText document
- * @param theWriter The direct iText writer
- * @param theStagesToVisit The stages to be visited by this strategy
- */
- public PartsListVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- for (PartsAccumulator partsAccumulator : crap.keySet()) {
- System.err.println(partsAccumulator.component.getComponentName() + " " + partsAccumulator.quantity);
- }
- }
-
+public class PartsListVisitorStrategy {
+
+ /**
+ * Accumulator for parts data.
+ */
+ private Map<PartsAccumulator, PartsAccumulator> crap = new HashMap<PartsAccumulator, PartsAccumulator>();
+
+ /**
+ * The iText document.
+ */
+ protected Document document;
+
+ /**
+ * The direct iText writer.
+ */
+ protected PdfWriter writer;
+
+ /**
+ * The stages selected.
+ */
+ protected Set<Integer> stages;
+
+
+ /**
+ * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStagesToVisit The stages to be visited by this strategy
+ */
+ public PartsListVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
+ }
+
+
+ /**
+ * Print the parts detail.
+ *
+ * @param root the root component
+ */
+ public void doVisit (final RocketComponent root) {
+ goDeep(root.getChildren());
+ }
+
+ /**
+ * 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) {
+ doIt(rocketComponent);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ private void doIt (final RocketComponent component) {
+ if (component instanceof InnerTube) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof LaunchLug) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ }
+
+ else if (component instanceof NoseCone) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof Transition) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RadiusRingComponent) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RingComponent) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof BodyTube) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof TrapezoidFinSet) {
+ }
+ else if (component instanceof EllipticalFinSet) {
+ }
+ else if (component instanceof FreeformFinSet) {
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close () {
+ for (PartsAccumulator partsAccumulator : crap.keySet()) {
+ System.err.println(partsAccumulator.component.getComponentName() + " " + partsAccumulator.quantity);
+ }
+ }
+
}
class PartsAccumulator {
-
- int quantity = 0;
-
- RocketComponent component;
-
- PartsAccumulator(RocketComponent theComponent) {
- component = theComponent;
- }
-
- void increment() {
- quantity++;
- }
-
- int quantity() {
- return quantity;
- }
-
- @Override
- public boolean equals(final Object o1) {
- if (this == o1) {
- return true;
- }
-
- RocketComponent that;
- if (o1 instanceof net.sf.openrocket.gui.print.visitor.PartsAccumulator) {
- that = ((net.sf.openrocket.gui.print.visitor.PartsAccumulator) o1).component;
- } else if (o1 instanceof RocketComponent) {
- that = (RocketComponent) o1;
- } else {
- return false;
- }
-
- if (this.component.getClass().equals(that.getClass())) {
- //If
- if (that.getLength() == this.component.getLength()) {
- if (that.getMass() == this.component.getMass()) {
- return true;
- }
- }
- if (this.component instanceof Coaxial &&
- that instanceof Coaxial) {
- Coaxial cThis = (Coaxial) this.component;
- Coaxial cThat = (Coaxial) that;
- if (cThis.getInnerRadius() == cThat.getInnerRadius() &&
- cThis.getOuterRadius() == cThat.getOuterRadius()) {
- return true;
- }
- }
- return false;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return component.getComponentName().hashCode();
- }
+
+ int quantity = 0;
+
+ RocketComponent component;
+
+ PartsAccumulator (RocketComponent theComponent) {
+ component = theComponent;
+ }
+
+ void increment () {
+ quantity++;
+ }
+
+ int quantity () {
+ return quantity;
+ }
+
+ @Override
+ public boolean equals (final Object o1) {
+ if (this == o1) {
+ return true;
+ }
+
+ RocketComponent that;
+ if (o1 instanceof net.sf.openrocket.gui.print.visitor.PartsAccumulator) {
+ that = ((net.sf.openrocket.gui.print.visitor.PartsAccumulator) o1).component;
+ }
+ else if (o1 instanceof RocketComponent) {
+ that = (RocketComponent) o1;
+ }
+ else {
+ return false;
+ }
+
+ if (this.component.getClass().equals(that.getClass())) {
+ //If
+ if (that.getLength() == this.component.getLength()) {
+ if (that.getMass() == this.component.getMass()) {
+ return true;
+ }
+ }
+ if (this.component instanceof Coaxial &&
+ that instanceof Coaxial) {
+ Coaxial cThis = (Coaxial) this.component;
+ Coaxial cThat = (Coaxial) that;
+ if (cThis.getInnerRadius() == cThat.getInnerRadius() &&
+ cThis.getOuterRadius() == cThat.getOuterRadius()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode () {
+ return component.getComponentName().hashCode();
+ }
}
\ No newline at end of file
+++ /dev/null
-/*
- * StageVisitor.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-/**
- * This visitor strategy accumulates Stage references in a Rocket hierarchy.
- */
-public class StageVisitorStrategy implements ComponentVisitorStrategy {
-
- /**
- * The collection of stages, accumulated during a visitation.
- */
- private List<Double> stageComponents = new ArrayList<Double>();
-
- private Double mass = 0d;
-
- /**
- * The owning visitor.
- */
- protected ComponentVisitor parent;
-
- /**
- * Constructor.
- */
- public StageVisitorStrategy() {
- }
-
- /**
- * Override the method that determines if the visiting should be going deep.
- *
- * @param stageNumber a stage number
- *
- * @return true, always
- */
- public boolean shouldVisitStage(int stageNumber) {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Rocket visitable) {
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
-
- if (mass > 0d) {
- stageComponents.add(mass);
- }
- mass = 0d;
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RocketComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * Recurse through the given rocket component.
- *
- * @param root the root component; all children will be visited recursively
- */
- protected void goDeep(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) {
- rocketComponent.accept(parent);
- }
- }
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * Get the list of stages, sort from Stage 1 .. Stage N.
- *
- * @return a sorted list of stages
- */
- public List<Double> getStages() {
- return stageComponents;
- }
-
- /**
- * Close by setting the last stage.
- */
- @Override
- public void close() {
- if (mass > 0d) {
- stageComponents.add(mass);
- }
- }
-
-}
/**
* Class to represent a body object. The object can be described as a function of
- * the cylindrical coordinates x and angle theta as r = f(x,theta). The component
+ * the cylindrical coordinates x and angle theta as r = f(x,theta). The component
* need not be symmetrical in any way (e.g. square tube, slanted cone etc).
- *
+ *
* It defines the methods getRadius(x,theta) and getInnerRadius(x,theta), as well
* as get/setLength().
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class BodyComponent extends ExternalComponent {
-
+
/**
* Default constructor. Sets the relative position to POSITION_RELATIVE_AFTER,
* i.e. body components come after one another.
public BodyComponent() {
super(RocketComponent.Position.AFTER);
}
-
-
+
+
/**
* Get the outer radius of the component at cylindrical coordinate (x,theta).
- *
+ *
* Note that the return value may be negative for a slanted object.
- *
+ *
* @param x Distance in x direction
* @param theta Angle about the x-axis
* @return Distance to the outer edge of the object
*/
public abstract double getRadius(double x, double theta);
-
-
+
+
/**
* Get the inner radius of the component at cylindrical coordinate (x,theta).
- *
+ *
* Note that the return value may be negative for a slanted object.
- *
+ *
* @param x Distance in x direction
* @param theta Angle about the x-axis
* @return Distance to the inner edge of the object
*/
public abstract double getInnerRadius(double x, double theta);
-
-
+
+
/**
* Sets the length of the body component.
this.length = Math.max(length, 0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
@Override
public boolean allowsChildren() {
return true;
}
-
- /**
- * Accept a visitor to this BodyComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this BodyComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
-
+
}
/**
* Rocket body tube component. Has only two parameters, a radius and length.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
-
+
private double radius = 0;
private boolean autoRadius = false; // Radius chosen automatically based on parent component
-
+
// When changing the inner radius, thickness is modified
-
+
private boolean motorMount = false;
private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
private HashMap<String, Motor> motors = new HashMap<String, Motor>();
private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
private double ignitionDelay = 0;
private double overhang = 0;
-
-
+
+
public BodyTube() {
super();
this.radius = DEFAULT_RADIUS;
this.autoRadius = true;
}
-
+
public BodyTube(double length, double radius) {
super();
this.radius = Math.max(radius, 0);
this.length = Math.max(length, 0);
}
-
-
+
+
public BodyTube(double length, double radius, boolean filled) {
this(length, radius);
this.filled = filled;
}
-
+
public BodyTube(double length, double radius, double thickness) {
this(length, radius);
this.filled = false;
this.thickness = thickness;
}
-
-
+
+
/************ Get/set component parameter methods ************/
-
+
/**
* Return the outer radius of the body tube.
- *
+ *
* @return the outside radius of the tube
*/
@Override
}
return radius;
}
-
-
+
+
/**
* Set the outer radius of the body tube. If the radius is less than the wall thickness,
* the wall thickness is decreased accordingly of the value of the radius.
* This method sets the automatic radius off.
- *
+ *
* @param radius the outside radius in standard units
*/
@Override
public void setOuterRadius (double radius) {
if ((this.radius == radius) && (autoRadius == false))
return;
-
+
this.autoRadius = false;
this.radius = Math.max(radius, 0);
-
+
if (this.thickness > this.radius)
this.thickness = this.radius;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
/**
* Returns whether the radius is selected automatically or not.
* Returns false also in case automatic radius selection is not possible.
public boolean isRadiusAutomatic() {
return autoRadius;
}
-
+
/**
- * Sets whether the radius is selected automatically or not.
+ * Sets whether the radius is selected automatically or not.
*/
public void setRadiusAutomatic(boolean auto) {
if (autoRadius == auto)
return;
-
+
autoRadius = auto;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public double getAftRadius() { return getOuterRadius(); }
@Override
public boolean isAftRadiusAutomatic() {
return isRadiusAutomatic();
}
-
+
@Override
public boolean isForeRadiusAutomatic() {
return isRadiusAutomatic();
}
-
-
+
+
@Override
protected double getFrontAutoRadius() {
}
return getOuterRadius();
}
-
+
@Override
protected double getRearAutoRadius() {
if (isRadiusAutomatic()) {
}
return getOuterRadius();
}
-
-
-
-
-
-
- @Override
+
+
+
+
+
+
+ @Override
public double getInnerRadius() {
if (filled)
return 0;
return Math.max(getOuterRadius()-thickness, 0);
}
-
+
@Override
public void setInnerRadius(double r) {
setThickness(getOuterRadius()-r);
}
-
-
+
+
/**
return "Body tube";
}
- /**
- * Accept a visitor to this BodyTube in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this BodyTube
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
/************ Component calculations ***********/
-
+
// From SymmetricComponent
/**
* Returns the outer radius at the position x. This returns the same value as getOuterRadius().
public double getRadius(double x) {
return getOuterRadius();
}
-
+
/**
* Returns the inner radius at the position x. If the tube is filled, returns always zero.
*/
else
return Math.max(getOuterRadius()-thickness,0);
}
-
-
+
+
/**
* Returns the body tube's center of gravity.
*/
public Coordinate getComponentCG() {
return new Coordinate(length / 2, 0, 0, getComponentMass());
}
-
+
/**
* Returns the body tube's volume.
*/
else
return getFilledVolume(r, length) - getFilledVolume(getInnerRadius(0), length);
}
-
-
+
+
@Override
public double getLongitudinalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
MathUtil.pow2(getLength())) / 12;
}
-
+
@Override
public double getRotationalUnitInertia() {
// 1/2 * (r1^2 + r2^2)
return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
}
-
-
+
+
/**
private static double getFilledVolume(double r, double l) {
return Math.PI * r * r * l;
}
-
-
+
+
/**
* Adds bounding coordinates to the given set. The body tube will fit within the
* convex hull of the points.
- *
+ *
* Currently the points are simply a rectangular box around the body tube.
*/
@Override
addBound(bounds, length, r);
return bounds;
}
-
-
+
+
/**
* Check whether the given type can be added to this component. BodyTubes allow any
* InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
return true;
return false;
}
-
+
//////////////// Motor mount /////////////////
-
+
@Override
public boolean isMotorMount() {
return motorMount;
}
-
+
@Override
public void setMotorMount(boolean mount) {
if (motorMount == mount)
motorMount = mount;
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Override
public Motor getMotor(String id) {
if (id == null)
return null;
-
+
// Check whether the id is valid for the current rocket
RocketComponent root = this.getRoot();
if (!(root instanceof Rocket))
return null;
if (!((Rocket) root).isMotorConfigurationID(id))
return null;
-
+
return motors.get(id);
}
-
+
@Override
public void setMotor(String id, Motor motor) {
if (id == null) {
motors.put(id, motor);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Override
public double getMotorDelay(String id) {
Double delay = ejectionDelays.get(id);
return Motor.PLUGGED;
return delay;
}
-
+
@Override
public void setMotorDelay(String id, double delay) {
ejectionDelays.put(id, delay);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Override
public int getMotorCount() {
return 1;
}
-
+
@Override
public double getMotorMountDiameter() {
return getInnerRadius() * 2;
}
-
+
@Override
public IgnitionEvent getIgnitionEvent() {
return ignitionEvent;
}
-
+
@Override
public void setIgnitionEvent(IgnitionEvent event) {
if (ignitionEvent == event)
ignitionEvent = event;
fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
}
-
-
+
+
@Override
public double getIgnitionDelay() {
return ignitionDelay;
}
-
+
@Override
public void setIgnitionDelay(double delay) {
if (MathUtil.equals(delay, ignitionDelay))
ignitionDelay = delay;
fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
}
-
-
+
+
@Override
public double getMotorOverhang() {
return overhang;
}
-
+
@Override
public void setMotorOverhang(double overhang) {
if (MathUtil.equals(this.overhang, overhang))
this.overhang = overhang;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public Coordinate getMotorPosition(String id) {
Motor motor = motors.get(id);
if (motor == null) {
throw new IllegalArgumentException("No motor with id " + id + " defined.");
}
-
+
return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
}
-
-
+
+
/*
* (non-Javadoc)
* Copy the motor and ejection delay HashMaps.
- *
+ *
* @see rocketcomponent.RocketComponent#copy()
*/
@SuppressWarnings("unchecked")
public class CenteringRing extends RadiusRingComponent {
-
+
public CenteringRing() {
setOuterRadiusAutomatic(true);
setInnerRadiusAutomatic(true);
setLength(0.002);
}
-
-
+
+
@Override
public double getInnerRadius() {
// Implement sibling inner radius automation
*/
if (!(sibling instanceof InnerTube)) // Excludes itself
continue;
-
+
double pos1 = this.toRelative(Coordinate.NUL, sibling)[0].x;
double pos2 = this.toRelative(new Coordinate(getLength()), sibling)[0].x;
if (pos2 < 0 || pos1 > sibling.getLength())
continue;
-
+
innerRadius = Math.max(innerRadius, ((InnerTube) sibling).getOuterRadius());
}
innerRadius = Math.min(innerRadius, getOuterRadius());
}
}
-
+
return super.getInnerRadius();
}
-
-
+
+
@Override
public void setOuterRadiusAutomatic(boolean auto) {
super.setOuterRadiusAutomatic(auto);
}
-
+
@Override
public void setInnerRadiusAutomatic(boolean auto) {
super.setInnerRadiusAutomatic(auto);
}
-
+
@Override
public String getComponentName() {
return "Centering ring";
}
-
+
@Override
public boolean allowsChildren() {
return false;
}
-
+
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
return false;
}
-
- /**
- * Accept a visitor to this CenteringRing in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this CenteringRing
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
+++ /dev/null
-/*
- * ComponentVisitor.java
- */
-package net.sf.openrocket.rocketcomponent;
-
-/**
- * This class implements a Visitor pattern to visit any/all components of a Rocket.
- */
-public class ComponentVisitor implements Visitor<ComponentVisitor, RocketComponent> {
-
- /**
- * The delegate.
- */
- private ComponentVisitorStrategy strategy;
-
- /**
- * Constructor.
- *
- * @param aStrategy the object to delegate the visiting to
- */
- public ComponentVisitor (ComponentVisitorStrategy aStrategy) {
- strategy = aStrategy;
- strategy.setParent(this);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final Rocket visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RocketComponent object. This is used to catch any RocketComponent subclass not explicity defined by a
- * visit method in this strategy.
- *
- * @param visitable the RocketComponent to visit
- */
- public void visit (final RocketComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Stage object.
- *
- * @param visitable the Stage to visit
- */
- public void visit (final Stage visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit an ExternalComponent object.
- *
- * @param visitable the ExternalComponent to visit
- */
- public void visit (final ExternalComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a BodyComponent object.
- *
- * @param visitable the BodyComponent to visit
- */
- public void visit (final BodyComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RingComponent object.
- *
- * @param visitable the RingComponent to visit
- */
- public void visit (final RingComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit an InnerTube object.
- *
- * @param visitable the InnerTube to visit
- */
- public void visit (final InnerTube visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a LaunchLug object.
- *
- * @param visitable the LaunchLug to visit
- */
- public void visit (final LaunchLug visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Transition object.
- *
- * @param visitable the Transition to visit
- */
- public void visit (final Transition visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RadiusRingComponent object.
- *
- * @param visitable the RadiusRingComponent to visit
- */
- public void visit (final RadiusRingComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a MassObject object.
- *
- * @param visitable the MassObject to visit
- */
- public void visit (final MassObject visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a NoseCone object.
- *
- * @param visitable the NoseCone to visit
- */
- public void visit (final NoseCone visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a BodyTube object.
- *
- * @param visitable the BodyTube to visit
- */
- public void visit (final BodyTube visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final TrapezoidFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final EllipticalFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a FreeformFinSet object.
- *
- * @param visitable the FreeformFinSet to visit
- */
- public void visit (final FreeformFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Perform any cleanup or finishing operations.
- */
- public void close () {
- strategy.close();
- }
-
-}
+++ /dev/null
-/*
- * ComponentVisitorStrategy.java
- */
-package net.sf.openrocket.rocketcomponent;
-
-/**
- * This interface defines the methods used in a Rocket component visitor. By using a strategy, we can reuse one visitor
- * definition and just instrument it with different strategies.
- */
-public interface ComponentVisitorStrategy {
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- void visit (final Rocket visitable);
-
- /**
- * Visit a RocketComponent object. This is used to catch any RocketComponent subclass not explicity defined by a
- * visit method in this strategy.
- *
- * @param visitable the RocketComponent to visit
- */
- void visit (final RocketComponent visitable);
-
- /**
- * Visit a Stage object.
- *
- * @param visitable the Stage to visit
- */
- void visit (final Stage visitable);
-
- /**
- * Visit an ExternalComponent object.
- *
- * @param visitable the ExternalComponent to visit
- */
- void visit (final ExternalComponent visitable);
-
- /**
- * Visit a BodyComponent object.
- *
- * @param visitable the BodyComponent to visit
- */
- void visit (final BodyComponent visitable);
-
- /**
- * Visit a RingComponent object.
- *
- * @param visitable the RingComponent to visit
- */
- void visit (final RingComponent visitable);
-
- /**
- * Visit an InnerTube object.
- *
- * @param visitable the InnerTube to visit
- */
- void visit (final InnerTube visitable);
-
- /**
- * Visit a LaunchLug object.
- *
- * @param visitable the LaunchLug to visit
- */
- void visit (final LaunchLug visitable);
-
- /**
- * Visit a Transition object.
- *
- * @param visitable the Transition to visit
- */
- void visit (final Transition visitable);
-
- /**
- * Visit a RadiusRingComponent object.
- *
- * @param visitable the RadiusRingComponent to visit
- */
- void visit (final RadiusRingComponent visitable);
-
- /**
- * Visit a MassComponent object.
- *
- * @param visitable the MassComponent to visit
- */
- void visit (final MassObject visitable);
-
- /**
- * Visit a NoseCone object.
- *
- * @param visitable the NoseCone to visit
- */
- void visit (final NoseCone visitable);
-
- /**
- * Visit a BodyTube object.
- *
- * @param visitable the BodyTube to visit
- */
- void visit (final BodyTube visitable);
-
- /**
- * Visit a TrapezoidFinSet object.
- *
- * @param visitable the TrapezoidFinSet to visit
- */
- void visit (final TrapezoidFinSet visitable);
-
- /**
- * Visit an EllipticalFinSet object.
- *
- * @param visitable the EllipticalFinSet to visit
- */
- void visit (final EllipticalFinSet visitable);
-
- /**
- * Visit a FreeformFinSet object.
- *
- * @param visitable the FreeformFinSet to visit
- */
- void visit (final FreeformFinSet visitable);
-
- /**
- * Set the visitor that is using this strategy.
- *
- * @param parent the visitor
- */
- void setParent (ComponentVisitor parent);
-
- /**
- * Perform any cleanup or finishing operations.
- */
- void close ();
-}
public class EllipticalFinSet extends FinSet {
public static final int POINTS = 21;
-
+
private static final double[] POINT_X = new double[POINTS];
private static final double[] POINT_Y = new double[POINTS];
static {
POINT_X[POINTS-1] = 1;
POINT_Y[POINTS-1] = 0;
}
-
-
+
+
private double height = 0.05;
-
+
public EllipticalFinSet() {
this.length = 0.05;
}
-
+
@Override
public Coordinate[] getFinPoints() {
Coordinate[] points = new Coordinate[POINTS];
public String getComponentName() {
return "Elliptical fin set";
}
-
-
+
+
public double getHeight() {
return height;
}
-
+
public void setHeight(double height) {
if (MathUtil.equals(this.height, height))
return;
this.height = height;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public void setLength(double length) {
if (MathUtil.equals(this.length, length))
return;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
- /**
- * Accept a visitor to this EllipticalFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this EllipticalFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
package net.sf.openrocket.rocketcomponent;
-import java.util.List;
-
import net.sf.openrocket.material.Material;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Prefs;
+import java.util.List;
+
/**
* Class of components with well-defined physical appearance and which have an effect on
- * aerodynamic simulation. They have material defined for them, and the mass of the component
+ * aerodynamic simulation. They have material defined for them, and the mass of the component
* is calculated using the component's volume.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class ExternalComponent extends RocketComponent {
-
+
public enum Finish {
ROUGH("Rough", 500e-6),
UNFINISHED("Unfinished", 150e-6),
NORMAL("Regular paint", 60e-6),
SMOOTH("Smooth paint", 20e-6),
POLISHED("Polished", 2e-6);
-
+
private final String name;
private final double roughnessSize;
-
+
Finish(String name, double roughness) {
this.name = name;
this.roughnessSize = roughness;
}
-
+
public double getRoughnessSize() {
return roughnessSize;
}
-
+
@Override
public String toString() {
return name + " (" + UnitGroup.UNITS_ROUGHNESS.toStringUnit(roughnessSize) + ")";
}
}
-
-
+
+
/**
* The material of the component.
*/
protected Material material = null;
-
+
protected Finish finish = Finish.NORMAL;
-
-
+
+
/**
* Constructor that sets the relative position of the component.
super(relativePosition);
this.material = Prefs.getDefaultComponentMaterial(this.getClass(), Material.Type.BULK);
}
-
+
/**
* Returns the volume of the component. This value is used in calculating the mass
* of the object.
*/
public abstract double getComponentVolume();
-
+
/**
* Calculates the mass of the component as the product of the volume and interior density.
*/
public double getComponentMass() {
return material.getDensity() * getComponentVolume();
}
-
+
/**
* ExternalComponent has aerodynamic effect, so return true.
*/
public boolean isAerodynamic() {
return true;
}
-
+
/**
* ExternalComponent has effect on the mass, so return true.
*/
public boolean isMassive() {
return true;
}
-
-
+
+
public Material getMaterial() {
return material;
}
-
+
public void setMaterial(Material mat) {
if (mat.getType() != Material.Type.BULK) {
throw new IllegalArgumentException("ExternalComponent requires a bulk material" +
" type=" + mat.getType());
}
-
+
if (material.equals(mat))
return;
material = mat;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public Finish getFinish() {
return finish;
}
-
+
public void setFinish(Finish finish) {
if (this.finish == finish)
return;
this.finish = finish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
-
+
@Override
protected List<RocketComponent> copyFrom(RocketComponent c) {
this.material = src.material;
return super.copyFrom(c);
}
-
- /**
- * Accept a visitor to this ExternalComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this ExternalComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
package net.sf.openrocket.rocketcomponent;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
public class FreeformFinSet extends FinSet {
private static final LogHelper log = Application.getLogger();
-
+
private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
-
+
public FreeformFinSet() {
points.add(Coordinate.NUL);
points.add(new Coordinate(0.025, 0.05));
points.add(new Coordinate(0.075, 0.05));
points.add(new Coordinate(0.05, 0));
-
+
this.length = 0.05;
}
-
-
+
+
public FreeformFinSet(Coordinate[] finpoints) throws IllegalFinPointException {
setPoints(finpoints);
}
-
+
/*
public FreeformFinSet(FinSet finset) {
Coordinate[] finpoints = finset.getFinPoints();
this.copyFrom(finset);
-
+
points.clear();
for (Coordinate c: finpoints) {
points.add(c);
* inserted in its stead.
* <p>
* The specified fin set should not be used after the call!
- *
+ *
* @param finset the fin set to convert.
* @return the new freeform fin set.
*/
final RocketComponent root = finset.getRoot();
FreeformFinSet freeform;
List<RocketComponent> toInvalidate = Collections.emptyList();
-
+
try {
if (root instanceof Rocket) {
((Rocket) root).freeze();
}
-
+
// Get fin set position and remove fin set
final RocketComponent parent = finset.getParent();
final int position;
} else {
position = -1;
}
-
+
// Create the freeform fin set
Coordinate[] finpoints = finset.getFinPoints();
"freeform fin, fin=" + finset + " points=" + Arrays.toString(finpoints),
e);
}
-
+
// Copy component attributes
toInvalidate = freeform.copyFrom(finset);
-
+
// Set name
final String componentTypeName = finset.getComponentName();
final String name = freeform.getName();
-
+
if (name.startsWith(componentTypeName)) {
freeform.setName(freeform.getComponentName() +
name.substring(componentTypeName.length()));
}
-
+
// Add freeform fin set to parent
if (parent != null) {
parent.addChild(freeform, position);
}
-
+
} finally {
if (root instanceof Rocket) {
((Rocket) root).thaw();
}
return freeform;
}
-
-
+
+
/**
* Add a fin point between indices <code>index-1</code> and <code>index</code>.
* The point is placed at the midpoint of the current segment.
- *
+ *
* @param index the fin point before which to add the new point.
*/
public void addPoint(int index) {
double x0, y0, x1, y1;
-
+
x0 = points.get(index - 1).x;
y0 = points.get(index - 1).y;
x1 = points.get(index).x;
y1 = points.get(index).y;
-
+
points.add(index, new Coordinate((x0 + x1) / 2, (y0 + y1) / 2));
// adding a point within the segment affects neither mass nor aerodynamics
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Remove the fin point with the given index. The first and last fin points
* cannot be removed, and will cause an <code>IllegalFinPointException</code>
* if attempted.
- *
+ *
* @param index the fin point index to remove
* @throws IllegalFinPointException if removing would result in invalid fin planform
*/
if (index == 0 || index == points.size() - 1) {
throw new IllegalFinPointException("cannot remove first or last point");
}
-
+
ArrayList<Coordinate> copy = this.points.clone();
copy.remove(index);
validate(copy);
this.points = copy;
-
+
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public int getPointCount() {
return points.size();
}
-
+
public void setPoints(Coordinate[] points) throws IllegalFinPointException {
ArrayList<Coordinate> list = new ArrayList<Coordinate>(points.length);
for (Coordinate p : points) {
}
validate(list);
this.points = list;
-
+
this.length = points[points.length - 1].x;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
/**
* Set the point at position <code>i</code> to coordinates (x,y).
* <p>
* Note that this method enforces basic fin shape restrictions (non-negative y,
- * first and last point locations) silently, but throws an
+ * first and last point locations) silently, but throws an
* <code>IllegalFinPointException</code> if the point causes fin segments to
* intersect.
* <p>
* Moving of the first point in the X-axis is allowed, but this actually moves
* all of the other points the corresponding distance back.
- *
+ *
* @param index the point index to modify.
* @param x the x-coordinate.
* @param y the y-coordinate.
public void setPoint(int index, double x, double y) throws IllegalFinPointException {
if (y < 0)
y = 0;
-
+
double x0, y0, x1, y1;
-
+
if (index == 0) {
-
+
// Restrict point
x = Math.min(x, points.get(points.size() - 1).x);
y = 0;
y0 = Double.NaN;
x1 = points.get(1).x;
y1 = points.get(1).y;
-
+
} else if (index == points.size() - 1) {
-
+
// Restrict point
x = Math.max(x, 0);
y = 0;
y0 = points.get(index - 1).y;
x1 = Double.NaN;
y1 = Double.NaN;
-
+
} else {
-
+
x0 = points.get(index - 1).x;
y0 = points.get(index - 1).y;
x1 = points.get(index + 1).x;
y1 = points.get(index + 1).y;
-
+
}
-
+
// Check for intersecting
for (int i = 1; i < points.size(); i++) {
px1 = points.get(i).x;
py1 = points.get(i).y;
-
+
if (i != index - 1 && i != index && i != index + 1) {
if (intersects(x0, y0, x, y, px0, py0, px1, py1)) {
throw new IllegalFinPointException("segments intersect");
throw new IllegalFinPointException("segments intersect");
}
}
-
+
px0 = px1;
py0 = py1;
}
-
+
if (index == 0) {
-
+
System.out.println("Set point zero to x:" + x);
for (int i = 1; i < points.size(); i++) {
Coordinate c = points.get(i);
points.set(i, c.setX(c.x - x));
}
-
+
} else {
-
+
points.set(index, new Coordinate(x, y));
-
+
}
if (index == 0 || index == points.size() - 1) {
this.length = points.get(points.size() - 1).x;
}
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
private boolean intersects(double ax0, double ay0, double ax1, double ay1,
double bx0, double by0, double bx1, double by1) {
-
+
double d = ((by1 - by0) * (ax1 - ax0) - (bx1 - bx0) * (ay1 - ay0));
-
+
double ua = ((bx1 - bx0) * (ay0 - by0) - (by1 - by0) * (ax0 - bx0)) / d;
double ub = ((ax1 - ax0) * (ay0 - by0) - (ay1 - ay0) * (ax0 - bx0)) / d;
-
+
return (ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1);
}
-
-
+
+
@Override
public Coordinate[] getFinPoints() {
return points.toArray(new Coordinate[0]);
}
-
+
@Override
public double getSpan() {
double max = 0;
}
return max;
}
-
+
@Override
public String getComponentName() {
return "Freeform fin set";
}
-
-
+
+
@Override
protected RocketComponent copyWithOriginalID() {
RocketComponent c = super.copyWithOriginalID();
((FreeformFinSet) c).points = this.points.clone();
return c;
}
-
- /**
- * Accept a visitor to this FreeformFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this FreeformFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
private void validate(ArrayList<Coordinate> points) throws IllegalFinPointException {
final int n = points.size();
if (points.get(0).x != 0 || points.get(0).y != 0 ||
}
}
}
-
+
}
/**
* This class defines an inner tube that can be used as a motor mount. The component
* may also be clustered.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class InnerTube extends ThicknessRingComponent
implements Clusterable, RadialParent, MotorMount {
-
+
private ClusterConfiguration cluster = ClusterConfiguration.SINGLE;
private double clusterScale = 1.0;
private double clusterRotation = 0.0;
-
+
private boolean motorMount = false;
private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
private double ignitionDelay = 0;
private double overhang = 0;
-
-
+
+
/**
* Main constructor.
*/
this.setInnerRadius(0.018 / 2);
this.setLength(0.070);
}
-
-
+
+
@Override
public double getInnerRadius(double x) {
return getInnerRadius();
}
-
-
+
+
@Override
public double getOuterRadius(double x) {
return getOuterRadius();
}
-
-
+
+
@Override
public String getComponentName() {
return "Inner Tube";
}
-
+
@Override
public boolean allowsChildren() {
return true;
}
-
+
/**
* Allow all InternalComponents to be added to this component.
*/
public boolean isCompatible(Class<? extends RocketComponent> type) {
return InternalComponent.class.isAssignableFrom(type);
}
-
-
+
+
///////////// Cluster methods //////////////
-
+
/**
* Get the current cluster configuration.
* @return The current cluster configuration.
public ClusterConfiguration getClusterConfiguration() {
return cluster;
}
-
+
/**
* Set the current cluster configuration.
* @param cluster The cluster configuration.
this.cluster = cluster;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return the number of tubes in the cluster.
* @return Number of tubes in the current cluster.
public int getClusterCount() {
return cluster.getClusterCount();
}
-
+
/**
* Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
* touching each other, larger values separate the tubes and smaller values
public double getClusterScale() {
return clusterScale;
}
-
+
/**
* Set the cluster scaling.
* @see #getClusterScale()
clusterScale = scale;
fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
}
-
-
+
+
/**
* @return the clusterRotation
public double getClusterRotation() {
return clusterRotation;
}
-
-
+
+
/**
* @param rotation the clusterRotation to set
*/
this.clusterRotation = rotation;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/**
* Return the distance between the closest two cluster inner tube center points.
* This is equivalent to the cluster scale multiplied by the tube diameter.
public double getClusterSeparation() {
return 2 * getOuterRadius() * clusterScale;
}
-
-
+
+
public List<Coordinate> getClusterPoints() {
List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount());
List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection());
}
return list;
}
-
-
+
+
@Override
public Coordinate[] shiftCoordinates(Coordinate[] array) {
array = super.shiftCoordinates(array);
-
+
int count = getClusterCount();
if (count == 1)
return array;
-
+
List<Coordinate> points = getClusterPoints();
if (points.size() != count) {
throw new BugException("Inconsistent cluster configuration, cluster count=" + count +
newArray[i * count + j] = array[i].add(points.get(j));
}
}
-
+
return newArray;
}
-
-
+
+
//////////////// Motor mount /////////////////
-
+
@Override
public boolean isMotorMount() {
return motorMount;
}
-
+
@Override
public void setMotorMount(boolean mount) {
if (motorMount == mount)
motorMount = mount;
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Override
public Motor getMotor(String id) {
if (id == null)
return null;
-
+
// Check whether the id is valid for the current rocket
RocketComponent root = this.getRoot();
if (!(root instanceof Rocket))
return null;
if (!((Rocket) root).isMotorConfigurationID(id))
return null;
-
+
return motors.get(id);
}
-
+
@Override
public void setMotor(String id, Motor motor) {
if (id == null) {
motors.put(id, motor);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Override
public double getMotorDelay(String id) {
Double delay = ejectionDelays.get(id);
return Motor.PLUGGED;
return delay;
}
-
+
@Override
public void setMotorDelay(String id, double delay) {
ejectionDelays.put(id, delay);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
+
@Deprecated
@Override
public int getMotorCount() {
return getClusterCount();
}
-
+
@Override
public double getMotorMountDiameter() {
return getInnerRadius() * 2;
}
-
+
@Override
public IgnitionEvent getIgnitionEvent() {
return ignitionEvent;
}
-
+
@Override
public void setIgnitionEvent(IgnitionEvent event) {
if (ignitionEvent == event)
ignitionEvent = event;
fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
}
-
-
+
+
@Override
public double getIgnitionDelay() {
return ignitionDelay;
}
-
+
@Override
public void setIgnitionDelay(double delay) {
if (MathUtil.equals(delay, ignitionDelay))
ignitionDelay = delay;
fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
}
-
-
+
+
@Override
public double getMotorOverhang() {
return overhang;
}
-
+
@Override
public void setMotorOverhang(double overhang) {
if (MathUtil.equals(this.overhang, overhang))
this.overhang = overhang;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public Coordinate getMotorPosition(String id) {
Motor motor = motors.get(id);
if (motor == null) {
throw new IllegalArgumentException("No motor with id " + id + " defined.");
}
-
+
return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
}
- /**
- * Accept a visitor to an InnerTube object in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this InnerTube
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
-
-
/*
* (non-Javadoc)
* Copy the motor and ejection delay HashMaps.
- *
+ *
* @see rocketcomponent.RocketComponent#copy()
*/
@SuppressWarnings("unchecked")
((InnerTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();
return c;
}
-
+
}
\ No newline at end of file
private double radius;
private double thickness;
-
+
private double radialDirection = 0;
-
+
/* These are calculated when the component is first attached to any Rocket */
private double shiftY, shiftZ;
-
-
+
+
public LaunchLug() {
super(Position.MIDDLE);
thickness = 0.001;
length = 0.03;
}
-
-
+
+
public double getOuterRadius () {
return radius;
}
-
+
public void setOuterRadius (double radius) {
if (MathUtil.equals(this.radius, radius))
return;
this.thickness = Math.min(this.thickness, this.radius);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getInnerRadius() {
return radius - thickness;
}
-
+
public void setInnerRadius(double innerRadius) {
setOuterRadius(innerRadius + thickness);
}
-
+
public double getThickness() {
return thickness;
}
-
+
public void setThickness(double thickness) {
if (MathUtil.equals(this.thickness, thickness))
return;
this.thickness = MathUtil.clamp(thickness, 0, radius);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public double getRadialDirection() {
return radialDirection;
}
-
+
public void setRadialDirection(double direction) {
direction = MathUtil.reduce180(direction);
if (MathUtil.equals(this.radialDirection, direction))
this.radialDirection = direction;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public void setLength(double length) {
if (MathUtil.equals(this.length, length))
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
super.setRelativePosition(position);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public void setPositionValue(double value) {
super.setPositionValue(value);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public Coordinate[] shiftCoordinates(Coordinate[] array) {
array = super.shiftCoordinates(array);
-
+
for (int i = 0; i < array.length; i++) {
array[i] = array[i].add(0, shiftY, shiftZ);
}
-
+
return array;
}
-
-
+
+
@Override
public void componentChanged(ComponentChangeEvent e) {
super.componentChanged(e);
-
- /*
+
+ /*
* shiftY and shiftZ must be computed here since calculating them
* in shiftCoordinates() would cause an infinite loop due to .toRelative
*/
RocketComponent body;
double parentRadius;
-
+
for (body = this.getParent(); body != null; body = body.getParent()) {
if (body instanceof SymmetricComponent)
break;
}
-
+
if (body == null) {
parentRadius = 0;
} else {
x2 = MathUtil.clamp(x2, 0, body.getLength());
parentRadius = Math.max(s.getRadius(x1), s.getRadius(x2));
}
-
+
shiftY = Math.cos(radialDirection) * (parentRadius + radius);
shiftZ = Math.sin(radialDirection) * (parentRadius + radius);
-
+
// System.out.println("Computed shift: y="+shiftY+" z="+shiftZ);
}
-
-
+
+
@Override
public double getComponentVolume() {
return length * Math.PI * (MathUtil.pow2(radius) - MathUtil.pow2(radius - thickness));
}
-
+
@Override
public Collection<Coordinate> getComponentBounds() {
ArrayList<Coordinate> set = new ArrayList<Coordinate>();
addBound(set, length, radius);
return set;
}
-
+
@Override
public Coordinate getComponentCG() {
return new Coordinate(length / 2, 0, 0, getComponentMass());
}
-
+
@Override
public String getComponentName() {
return "Launch lug";
}
-
+
@Override
public double getLongitudinalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
MathUtil.pow2(getLength())) / 12;
}
-
+
@Override
public double getRotationalUnitInertia() {
// 1/2 * (r1^2 + r2^2)
return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
}
-
+
@Override
public boolean allowsChildren() {
return false;
}
-
+
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
// Allow nothing to be attached to a LaunchLug
return false;
}
-
- /**
- * Accept a visitor to this LaunchLug in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this LaunchLug
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
package net.sf.openrocket.rocketcomponent;
-import static net.sf.openrocket.util.MathUtil.pow2;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
import java.util.ArrayList;
import java.util.Collection;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
+import static net.sf.openrocket.util.MathUtil.pow2;
/**
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class MassObject extends InternalComponent {
-
+
private double radius;
-
+
private double radialPosition;
private double radialDirection;
-
+
private double shiftY = 0;
private double shiftZ = 0;
-
-
+
+
public MassObject() {
this(0.03, 0.015);
}
-
+
public MassObject(double length, double radius) {
super();
-
+
this.length = length;
this.radius = radius;
-
+
this.setRelativePosition(Position.TOP);
this.setPositionValue(0.0);
}
-
-
+
+
public void setLength(double length) {
length = Math.max(length, 0);
if (MathUtil.equals(this.length, length)) {
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final double getRadius() {
return radius;
}
-
-
+
+
public final void setRadius(double radius) {
radius = Math.max(radius, 0);
if (MathUtil.equals(this.radius, radius)) {
this.radius = radius;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final double getRadialPosition() {
return radialPosition;
}
-
+
public final void setRadialPosition(double radialPosition) {
radialPosition = Math.max(radialPosition, 0);
if (MathUtil.equals(this.radialPosition, radialPosition)) {
shiftZ = radialPosition * Math.sin(radialDirection);
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public final double getRadialDirection() {
return radialDirection;
}
-
+
public final void setRadialDirection(double radialDirection) {
radialDirection = MathUtil.reduce180(radialDirection);
if (MathUtil.equals(this.radialDirection, radialDirection)) {
shiftZ = radialPosition * Math.sin(radialDirection);
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/**
* Shift the coordinates according to the radial position and direction.
*/
}
return array;
}
-
+
@Override
public final Coordinate getComponentCG() {
return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
}
-
+
@Override
public final double getLongitudinalUnitInertia() {
return (3 * pow2(radius) + pow2(length)) / 12;
}
-
+
@Override
public final double getRotationalUnitInertia() {
return pow2(radius) / 2;
}
-
+
@Override
public final Collection<Coordinate> getComponentBounds() {
Collection<Coordinate> c = new ArrayList<Coordinate>();
addBound(c, length, radius);
return c;
}
-
- /**
- * Accept a visitor to this MassObject in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this MassObject
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
/**
* Rocket nose cones of various types. Implemented as a transition with the
* fore radius == 0.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class NoseCone extends Transition {
-
-
+
+
/********* Constructors **********/
public NoseCone() {
this(Transition.Shape.OGIVE, 6*DEFAULT_RADIUS, DEFAULT_RADIUS);
}
-
+
public NoseCone(Transition.Shape type, double length, double radius) {
super();
super.setType(type);
super.setThickness(0.002);
super.setLength(length);
super.setClipped(false);
-
+
}
-
+
/********** Get/set methods for component parameters **********/
public double getForeRadius() {
return 0;
}
-
+
@Override
public void setForeRadius(double r) {
// No-op
public boolean isForeRadiusAutomatic() {
return false;
}
-
+
@Override
public void setForeRadiusAutomatic(boolean b) {
// No-op
public boolean isClipped() {
return false;
}
-
+
@Override
public void setClipped(boolean b) {
// No-op
}
-
-
+
+
/********** RocketComponent methods **********/
/**
public String getComponentName() {
return "Nose cone";
}
-
- /**
- * Accept a visitor to this NoseCone in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this NoseCone
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
+
}
/**
* An inner component that consists of a hollow cylindrical component. This can be
* an inner tube, tube coupler, centering ring, bulkhead etc.
- *
+ *
* The properties include the inner and outer radii, length and radial position.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class RadiusRingComponent extends RingComponent implements Coaxial {
protected double outerRadius = 0;
protected double innerRadius = 0;
-
+
@Override
public double getOuterRadius() {
if (outerRadiusAutomatic && getParent() instanceof RadialParent) {
outerRadius = Math.min(((RadialParent)parent).getInnerRadius(pos1),
((RadialParent)parent).getInnerRadius(pos2));
}
-
+
return outerRadius;
}
r = Math.max(r,0);
if (MathUtil.equals(outerRadius, r) && !isOuterRadiusAutomatic())
return;
-
+
outerRadius = r;
outerRadiusAutomatic = false;
if (getInnerRadius() > r) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
@Override
public double getInnerRadius() {
return innerRadius;
r = Math.max(r,0);
if (MathUtil.equals(innerRadius, r))
return;
-
+
innerRadius = r;
innerRadiusAutomatic = false;
if (getOuterRadius() < r) {
outerRadius = r;
outerRadiusAutomatic = false;
}
-
+
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
@Override
public double getThickness() {
return Math.max(getOuterRadius() - getInnerRadius(), 0);
@Override
public void setThickness(double thickness) {
double outer = getOuterRadius();
-
+
thickness = MathUtil.clamp(thickness, 0, outer);
setInnerRadius(outer - thickness);
}
- /**
- * Accept a visitor to this RadiusRingComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RadiusRingComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
}
/**
* An inner component that consists of a hollow cylindrical component. This can be
* an inner tube, tube coupler, centering ring, bulkhead etc.
- *
+ *
* The properties include the inner and outer radii, length and radial position.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class RingComponent extends StructuralComponent implements Coaxial {
protected boolean outerRadiusAutomatic = false;
protected boolean innerRadiusAutomatic = false;
-
-
+
+
private double radialDirection = 0;
private double radialPosition = 0;
-
+
private double shiftY = 0;
private double shiftZ = 0;
-
-
+
+
@Override
public abstract double getOuterRadius();
@Override
public abstract void setOuterRadius(double r);
-
+
@Override
- public abstract double getInnerRadius();
+ public abstract double getInnerRadius();
@Override
public abstract void setInnerRadius(double r);
-
+
@Override
public abstract double getThickness();
public abstract void setThickness(double thickness);
-
-
+
+
public final boolean isOuterRadiusAutomatic() {
return outerRadiusAutomatic;
}
-
+
protected void setOuterRadiusAutomatic(boolean auto) {
if (auto == outerRadiusAutomatic)
return;
outerRadiusAutomatic = auto;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final boolean isInnerRadiusAutomatic() {
return innerRadiusAutomatic;
}
-
+
protected void setInnerRadiusAutomatic(boolean auto) {
if (auto == innerRadiusAutomatic)
return;
innerRadiusAutomatic = auto;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
-
-
+
+
+
+
public final void setLength(double length) {
double l = Math.max(length,0);
if (this.length == l)
return;
-
+
this.length = l;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return the radial direction of displacement of the component. Direction 0
* is equivalent to the Y-direction.
- *
+ *
* @return the radial direction.
*/
public double getRadialDirection() {
return radialDirection;
}
-
+
/**
* Set the radial direction of displacement of the component. Direction 0
* is equivalent to the Y-direction.
- *
+ *
* @param dir the radial direction.
*/
public void setRadialDirection(double dir) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
-
+
+
+
/**
* Return the radial position of the component. The position is the distance
* of the center of the component from the center of the parent component.
- *
+ *
* @return the radial position.
*/
public double getRadialPosition() {
return radialPosition;
}
-
+
/**
* Set the radial position of the component. The position is the distance
* of the center of the component from the center of the parent component.
- *
+ *
* @param pos the radial position.
*/
public void setRadialPosition(double pos) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getRadialShiftY() {
return shiftY;
}
-
+
public double getRadialShiftZ() {
return shiftZ;
}
-
+
public void setRadialShift(double y, double z) {
radialPosition = Math.hypot(y, z);
radialDirection = Math.atan2(z, y);
-
- // Re-calculate to ensure consistency
+
+ // Re-calculate to ensure consistency
shiftY = radialPosition * Math.cos(radialDirection);
shiftZ = radialPosition * Math.sin(radialDirection);
assert(MathUtil.equals(y, shiftY));
assert(MathUtil.equals(z, shiftZ));
-
+
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
return ((Clusterable)this).getClusterConfiguration().getClusterCount();
return 1;
}
-
-
+
+
/**
* Shift the coordinates according to the radial position and direction.
*/
}
return array;
}
-
-
+
+
@Override
public Collection<Coordinate> getComponentBounds() {
List<Coordinate> bounds = new ArrayList<Coordinate>();
addBound(bounds,length,getOuterRadius());
return bounds;
}
-
-
+
+
@Override
public Coordinate getComponentCG() {
return new Coordinate(length/2, 0, 0, getComponentMass());
return ringMass(getOuterRadius(), getInnerRadius(), getLength(),
getMaterial().getDensity()) * getClusterCount();
}
-
+
@Override
public double getLongitudinalUnitInertia() {
return ringRotationalUnitInertia(getOuterRadius(), getInnerRadius());
}
- /**
- * Accept a visitor to this RingComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RingComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
}
package net.sf.openrocket.rocketcomponent;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.swing.event.ChangeListener;
-import javax.swing.event.EventListenerList;
-
import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
-import net.sf.openrocket.util.Chars;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.UniqueID;
+import net.sf.openrocket.util.*;
+
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+import java.util.*;
/**
* (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
* It also defines some other methods that concern the whole rocket, and helper methods
* that keep information about the program state.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class Rocket extends RocketComponent {
private static final LogHelper log = Application.getLogger();
-
+
public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
-
+
/**
* List of component change listeners.
*/
private EventListenerList listenerList = new EventListenerList();
-
+
/**
* When freezeList != null, events are not dispatched but stored in the list.
* When the structure is thawed, a single combined event will be fired.
*/
private List<ComponentChangeEvent> freezeList = null;
-
+
private int modID;
private int massModID;
private int aeroModID;
private int treeModID;
private int functionalModID;
-
+
private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
-
+
// The default configuration used in dialogs
private final Configuration defaultConfiguration;
-
+
private String designer = "";
private String revision = "";
-
+
// Motor configuration list
private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
{
motorConfigurationIDs.add(null);
}
-
+
// Does the rocket have a perfect finish (a notable amount of laminar flow)
private boolean perfectFinish = false;
-
-
+
+
///////////// Constructor /////////////
-
+
public Rocket() {
super(RocketComponent.Position.AFTER);
modID = UniqueID.next();
functionalModID = modID;
defaultConfiguration = new Configuration(this);
}
-
-
+
+
public String getDesigner() {
checkState();
return designer;
}
-
+
public void setDesigner(String s) {
if (s == null)
s = "";
designer = s;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
public String getRevision() {
checkState();
return revision;
}
-
+
public void setRevision(String s) {
if (s == null)
s = "";
revision = s;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Return the number of stages in this rocket.
- *
+ *
* @return the number of stages in this rocket.
*/
public int getStageCount() {
checkState();
return this.getChildCount();
}
-
-
+
+
/**
* Return the non-negative modification ID of this rocket. The ID is changed
- * every time any change occurs in the rocket. This can be used to check
+ * every time any change occurs in the rocket. This can be used to check
* whether it is necessary to void cached data in cases where listeners can not
* or should not be used.
* <p>
* Three other modification IDs are also available, {@link #getMassModID()},
- * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time
- * a mass change, aerodynamic change, or tree change occur. Even though the values
- * of the different modification ID's may be equal, they should be treated totally
+ * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time
+ * a mass change, aerodynamic change, or tree change occur. Even though the values
+ * of the different modification ID's may be equal, they should be treated totally
* separate.
* <p>
* Note that undo events restore the modification IDs that were in use at the
* corresponding undo level. Subsequent modifications, however, produce modIDs
* distinct from those already used.
- *
+ *
* @return a unique ID number for this modification state.
*/
public int getModID() {
return modID;
}
-
+
/**
* Return the non-negative mass modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this mass-modification state.
*/
public int getMassModID() {
return massModID;
}
-
+
/**
* Return the non-negative aerodynamic modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this aerodynamic-modification state.
*/
public int getAerodynamicModID() {
return aeroModID;
}
-
+
/**
* Return the non-negative tree modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this tree-modification state.
*/
public int getTreeModID() {
return treeModID;
}
-
+
/**
* Return the non-negative functional modificationID of this rocket.
* This changes every time a functional change occurs.
- *
+ *
* @return a unique ID number for this functional modification state.
*/
public int getFunctionalModID() {
return functionalModID;
}
-
-
+
+
public ReferenceType getReferenceType() {
checkState();
return refType;
}
-
+
public void setReferenceType(ReferenceType type) {
if (refType == type)
return;
refType = type;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
public double getCustomReferenceLength() {
checkState();
return customReferenceLength;
}
-
+
public void setCustomReferenceLength(double length) {
if (MathUtil.equals(customReferenceLength, length))
return;
-
+
this.customReferenceLength = Math.max(length, 0.001);
-
+
if (refType == ReferenceType.CUSTOM) {
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
}
-
-
+
+
/**
* Set whether the rocket has a perfect finish. This will affect whether the
* boundary layer is assumed to be fully turbulent or not.
- *
+ *
* @param perfectFinish whether the finish is perfect.
*/
public void setPerfectFinish(boolean perfectFinish) {
this.perfectFinish = perfectFinish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
-
+
+
/**
* Get whether the rocket has a perfect finish.
- *
+ *
* @return the perfectFinish
*/
public boolean isPerfectFinish() {
return perfectFinish;
}
-
-
+
+
copy.motorConfigurationNames =
(HashMap<String, String>) this.motorConfigurationNames.clone();
copy.resetListeners();
-
+
return copy;
}
-
+
/**
* Load the rocket structure from the source. The method loads the fields of this
* Rocket object and copies the references to siblings from the <code>source</code>.
*/
@SuppressWarnings("unchecked")
public void loadFrom(Rocket r) {
-
+
// Store list of components to invalidate after event has been fired
List<RocketComponent> toInvalidate = this.copyFrom(r);
-
+
int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
if (this.massModID != r.massModID)
type |= ComponentChangeEvent.MASS_CHANGE;
type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
// Loading a rocket is always a tree change since the component objects change
type |= ComponentChangeEvent.TREE_CHANGE;
-
+
this.modID = r.modID;
this.massModID = r.massModID;
this.aeroModID = r.aeroModID;
this.functionalModID = r.functionalModID;
this.refType = r.refType;
this.customReferenceLength = r.customReferenceLength;
-
+
this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
this.motorConfigurationNames =
(HashMap<String, String>) r.motorConfigurationNames.clone();
this.perfectFinish = r.perfectFinish;
-
+
String id = defaultConfiguration.getMotorConfigurationID();
if (!this.motorConfigurationIDs.contains(id))
defaultConfiguration.setMotorConfigurationID(null);
-
+
this.checkComponentStructure();
-
+
fireComponentChangeEvent(type);
-
+
// Invalidate obsolete components after event
for (RocketComponent c : toInvalidate) {
c.invalidate();
}
}
-
-
+
+
/////// Implement the ComponentChangeListener lists
-
+
/**
* Creates a new EventListenerList for this component. This is necessary when cloning
* the structure.
// System.out.println("RESETTING LISTENER LIST of Rocket "+this);
listenerList = new EventListenerList();
}
-
-
+
+
public void printListeners() {
System.out.println("" + this + " has " + listenerList.getListenerCount() + " listeners:");
Object[] list = listenerList.getListenerList();
for (int i = 1; i < list.length; i += 2)
System.out.println(" " + ((i + 1) / 2) + ": " + list[i]);
}
-
+
@Override
public void addComponentChangeListener(ComponentChangeListener l) {
checkState();
log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
+
@Override
public void removeComponentChangeListener(ComponentChangeListener l) {
listenerList.remove(ComponentChangeListener.class, l);
log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
-
+
+
@Override
public void addChangeListener(ChangeListener l) {
checkState();
log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
+
@Override
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
-
+
+
@Override
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
mutex.lock("fireComponentChangeEvent");
try {
checkState();
-
+
// Update modification ID's only for normal (not undo/redo) events
if (!e.isUndoChange()) {
modID = UniqueID.next();
if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
functionalModID = modID;
}
-
+
// Check whether frozen
if (freezeList != null) {
log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
freezeList.add(e);
return;
}
-
+
log.debug("Firing rocket change event " + e);
-
+
// Notify all components first
Iterator<RocketComponent> iterator = this.iterator(true);
while (iterator.hasNext()) {
iterator.next().componentChanged(e);
}
-
+
// Notify all listeners
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
mutex.unlock("fireComponentChangeEvent");
}
}
-
-
+
+
/**
* Freezes the rocket structure from firing any events. This may be performed to
* combine several actions on the structure into a single large action.
* <code>thaw()</code> must always be called afterwards.
- *
+ *
* NOTE: Always use a try/finally to ensure <code>thaw()</code> is called:
* <pre>
* Rocket r = c.getRocket();
* r.thaw();
* }
* </pre>
- *
+ *
* @see #thaw()
*/
public void freeze() {
"freezeList=" + freezeList);
}
}
-
+
/**
* Thaws a frozen rocket structure and fires a combination of the events fired during
* the freeze. The event type is a combination of those fired and the source is the
freezeList = null;
return;
}
-
+
log.debug("Thawing rocket, freezeList=" + freezeList);
-
+
int type = 0;
Object c = null;
for (ComponentChangeEvent e : freezeList) {
c = e.getSource();
}
freezeList = null;
-
+
fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
}
-
-
+
+
//////// Motor configurations ////////
-
+
/**
* Return the default configuration. This should be used in the user interface
* to ensure a consistent rocket configuration between dialogs. It should NOT
* be used in simulations not relating to the UI.
- *
+ *
* @return the default {@link Configuration}.
*/
public Configuration getDefaultConfiguration() {
checkState();
return defaultConfiguration;
}
-
-
+
+
/**
* Return an array of the motor configuration IDs. This array is guaranteed
* to contain the <code>null</code> ID as the first element.
- *
+ *
* @return an array of the motor configuration IDs.
*/
public String[] getMotorConfigurationIDs() {
checkState();
return motorConfigurationIDs.toArray(new String[0]);
}
-
+
/**
* Add a new motor configuration ID to the motor configurations. The new ID
* is returned.
- *
+ *
* @return the new motor configuration ID.
*/
public String newMotorConfigurationID() {
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
return id;
}
-
+
/**
* Add a specified motor configuration ID to the motor configurations.
- *
+ *
* @param id the motor configuration ID.
* @return true if successful, false if the ID was already used.
*/
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
return true;
}
-
+
/**
* Remove a motor configuration ID from the configuration IDs. The <code>null</code>
* ID cannot be removed, and an attempt to remove it will be silently ignored.
- *
+ *
* @param id the motor configuration ID to remove
*/
public void removeMotorConfigurationID(String id) {
motorConfigurationIDs.remove(id);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
-
+
+
/**
* Check whether <code>id</code> is a valid motor configuration ID.
- *
+ *
* @param id the configuration ID.
* @return whether a motor configuration with that ID exists.
*/
checkState();
return motorConfigurationIDs.contains(id);
}
-
-
+
+
/**
* Check whether the given motor configuration ID has motors defined for it.
- *
+ *
* @param id the motor configuration ID (may be invalid).
* @return whether any motors are defined for it.
*/
checkState();
if (id == null)
return false;
-
+
Iterator<RocketComponent> iterator = this.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
-
+
if (c instanceof MotorMount) {
MotorMount mount = (MotorMount) c;
if (!mount.isMotorMount())
}
return false;
}
-
-
+
+
/**
* Return the user-set name of the motor configuration. If no name has been set,
* returns an empty string (not null).
- *
+ *
* @param id the motor configuration id
* @return the configuration name
*/
return "";
return s;
}
-
-
+
+
/**
* Set the name of the motor configuration. A name can be unset by passing
* <code>null</code> or an empty string.
- *
+ *
* @param id the motor configuration id
* @param name the name for the motor configuration
*/
motorConfigurationNames.put(id, name);
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
- * Return either the motor configuration name (if set) or its description.
- *
+ * Return either the motor configuration name (if set) or its description.
+ *
* @param id the motor configuration ID.
* @return a textual representation of the configuration
*/
public String getMotorConfigurationNameOrDescription(String id) {
checkState();
String name;
-
+
name = getMotorConfigurationName(id);
if (name != null && !name.equals(""))
return name;
-
+
return getMotorConfigurationDescription(id);
}
-
+
/**
- * Return a description for the motor configuration, generated from the motor
+ * Return a description for the motor configuration, generated from the motor
* designations of the components.
- *
+ *
* @param id the motor configuration ID.
* @return a textual representation of the configuration
*/
checkState();
String name;
int motorCount = 0;
-
+
// Generate the description
-
+
// First iterate over each stage and store the designations of each motor
List<List<String>> list = new ArrayList<List<String>>();
List<String> currentList = null;
-
+
Iterator<RocketComponent> iterator = this.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
-
+
if (c instanceof Stage) {
-
+
currentList = new ArrayList<String>();
list.add(currentList);
-
+
} else if (c instanceof MotorMount) {
-
+
MotorMount mount = (MotorMount) c;
Motor motor = mount.getMotor(id);
-
+
if (mount.isMotorMount() && motor != null) {
String designation = motor.getDesignation(mount.getMotorDelay(id));
-
+
for (int i = 0; i < mount.getMotorCount(); i++) {
currentList.add(designation);
motorCount++;
}
}
-
+
}
}
-
+
if (motorCount == 0) {
return "[No motors]";
}
-
- // Change multiple occurrences of a motor to n x motor
+
+ // Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<String>();
-
+
for (List<String> stage : list) {
String stageName = "";
String previous = null;
int count = 0;
-
+
Collections.sort(stage);
for (String current : stage) {
if (current.equals(previous)) {
-
+
count++;
-
+
} else {
-
+
if (previous != null) {
String s = "";
if (count > 1) {
} else {
s = previous;
}
-
+
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
-
+
previous = current;
count = 1;
-
+
}
}
if (previous != null) {
} else {
s = previous;
}
-
+
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
-
+
stages.add(stageName);
}
-
+
name = "[";
for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
name += "]";
return name;
}
-
-
+
+
//////// Obligatory component information
-
+
@Override
public String getComponentName() {
return "Rocket";
}
-
+
@Override
public Coordinate getComponentCG() {
return new Coordinate(0, 0, 0, 0);
}
-
+
@Override
public double getComponentMass() {
return 0;
}
-
+
@Override
public double getLongitudinalUnitInertia() {
return 0;
}
-
+
@Override
public double getRotationalUnitInertia() {
return 0;
}
-
+
@Override
public Collection<Coordinate> getComponentBounds() {
return Collections.emptyList();
}
-
+
@Override
public boolean isAerodynamic() {
return false;
}
-
+
@Override
public boolean isMassive() {
return false;
}
-
+
@Override
public boolean allowsChildren() {
return true;
}
-
+
/**
* Allows only <code>Stage</code> components to be added to the type Rocket.
*/
public boolean isCompatible(Class<? extends RocketComponent> type) {
return (Stage.class.isAssignableFrom(type));
}
-
- /**
- * Accept a visitor to this Rocket in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Rocket
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
+
}
package net.sf.openrocket.rocketcomponent;
-import java.awt.Color;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-import javax.swing.event.ChangeListener;
-
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.ChangeSource;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.Invalidator;
-import net.sf.openrocket.util.LineStyle;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.SafetyMutex;
-import net.sf.openrocket.util.UniqueID;
-
-
-public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent>,
- Visitable<ComponentVisitor, RocketComponent> {
+import net.sf.openrocket.util.*;
+
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+
+public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
private static final LogHelper log = Application.getLogger();
-
+
/*
* Text is suitable to the form
* Position relative to: <title>
AFTER("After the parent component"),
/** Specify an absolute X-coordinate position. */
ABSOLUTE("Tip of the nose cone");
-
+
private String title;
-
+
Position(String title) {
this.title = title;
}
-
+
@Override
public String toString() {
return title;
}
}
-
+
/**
* A safety mutex that can be used to prevent concurrent access to this component.
*/
protected SafetyMutex mutex = SafetyMutex.newInstance();
-
+
//////// Parent/child trees
/**
* Parent component of the current component, or null if none exists.
*/
private RocketComponent parent = null;
-
+
/**
* List of child components of this component.
*/
private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
-
+
//////// Parameters common to all components:
-
+
/**
* Characteristic length of the component. This is used in calculating the coordinate
* transformations and positions of other components in reference to this component.
* By default it is zero, i.e. no translation.
*/
protected double length = 0;
-
+
/**
* Positioning of this component relative to the parent component.
*/
protected Position relativePosition;
-
+
/**
* Offset of the position of this component relative to the normal position given by
* relativePosition. By default zero, i.e. no position change.
*/
protected double position = 0;
-
+
// Color of the component, null means to use the default color
private Color color = null;
private LineStyle lineStyle = null;
-
+
// Override mass/CG
private double overrideMass = 0;
private boolean massOverriden = false;
private double overrideCGX = 0;
private boolean cgOverriden = false;
-
+
private boolean overrideSubcomponents = false;
-
+
// User-given name of the component
private String name = null;
-
+
// User-specified comment
private String comment = "";
-
+
// Unique ID of the component
private String id = null;
-
+
/**
* Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
*/
private Invalidator invalidator = new Invalidator(this);
-
-
+
+
//// NOTE !!! All fields must be copied in the method copyFrom()! ////
-
+
/**
this.relativePosition = relativePosition;
newID();
}
-
+
//////////// Methods that must be implemented ////////////
-
+
/**
* Static component name. The name may not vary of the parameters, it must be static.
*/
public abstract String getComponentName(); // Static component type name
-
+
/**
* Return the component mass (regardless of mass overriding).
*/
public abstract double getComponentMass(); // Mass of non-overridden component
-
+
/**
* Return the component CG and mass (regardless of CG or mass overriding).
*/
public abstract Coordinate getComponentCG(); // CG of non-overridden component
-
+
/**
- * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
+ * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
* The unitary moment of inertia is the moment of inertia with the assumption that
* the mass of the component is one kilogram. The inertia is measured in
* respect to the non-overridden CG.
- *
+ *
* @return the longitudinal unitary moment of inertia of this component.
*/
public abstract double getLongitudinalUnitInertia();
-
-
+
+
/**
- * Return the rotational (around the x-axis) unitary moment of inertia.
+ * Return the rotational (around the x-axis) unitary moment of inertia.
* The unitary moment of inertia is the moment of inertia with the assumption that
* the mass of the component is one kilogram. The inertia is measured in
* respect to the non-overridden CG.
- *
+ *
* @return the rotational unitary moment of inertia of this component.
*/
public abstract double getRotationalUnitInertia();
-
-
+
+
/**
* Test whether this component allows any children components. This method must
* return true if and only if {@link #isCompatible(Class)} returns true for any
* rocket component class.
- *
+ *
* @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
*/
public abstract boolean allowsChildren();
-
+
/**
* Test whether the given component type can be added to this component. This type safety
* is enforced by the <code>addChild()</code> methods. The return value of this method
* may change to reflect the current state of this component (e.g. two components of some
* type cannot be placed as children).
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
public abstract boolean isCompatible(Class<? extends RocketComponent> type);
-
-
+
+
/* Non-abstract helper method */
/**
* Test whether the given component can be added to this component. This is equivalent
* to calling <code>isCompatible(c.getClass())</code>.
- *
+ *
* @param c Component to test.
* @return Whether the component can be added.
* @see #isCompatible(Class)
mutex.verify();
return isCompatible(c.getClass());
}
-
-
+
+
/**
* Return a collection of bounding coordinates. The coordinates must be such that
* the component is fully enclosed in their convex hull.
- *
+ *
* @return a collection of coordinates that bound the component.
*/
public abstract Collection<Coordinate> getComponentBounds();
-
+
/**
* Return true if the component may have an aerodynamic effect on the rocket.
*/
public abstract boolean isAerodynamic();
-
+
/**
* Return true if the component may have an effect on the rocket's mass.
*/
public abstract boolean isMassive();
-
-
+
+
//////////// Methods that may be overridden ////////////
-
+
/**
* Shift the coordinates in the array corresponding to radial movement. A component
* coordinate for each cluster.
* <p>
* The default implementation simply returns the array, and thus produces no shift.
- *
+ *
* @param c an array of coordinates to shift.
* @return an array of shifted coordinates. The method may modify the contents
* of the passed array and return the array itself.
checkState();
return c;
}
-
-
+
+
/**
- * Called when any component in the tree fires a ComponentChangeEvent. This is by
- * default a no-op, but subclasses may override this method to e.g. invalidate
- * cached data. The overriding method *must* call
+ * Called when any component in the tree fires a ComponentChangeEvent. This is by
+ * default a no-op, but subclasses may override this method to e.g. invalidate
+ * cached data. The overriding method *must* call
* <code>super.componentChanged(e)</code> at some point.
- *
+ *
* @param e The event fired
*/
protected void componentChanged(ComponentChangeEvent e) {
// No-op
checkState();
}
-
-
+
+
/**
* Return the user-provided name of the component, or the component base
* name if the user-provided name is empty. This can be used in the UI.
- *
+ *
* @return A string describing the component.
*/
@Override
else
return name;
}
-
-
+
+
/**
* Create a string describing the basic component structure from this component downwards.
* @return a string containing the rocket structure
mutex.unlock("toDebugString");
}
}
-
+
private void toDebugString(StringBuilder sb) {
sb.append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this));
sb.append("[\"").append(this.getName()).append('"');
}
sb.append(']');
}
-
-
+
+
/**
* Make a deep copy of the rocket component tree structure from this component
* downwards for copying purposes. Each component in the copy will be assigned
* a new component ID, making it a safe copy. This method does not fire any events.
- *
+ *
* @return A deep copy of the structure.
*/
public final RocketComponent copy() {
RocketComponent clone = copyWithOriginalID();
-
+
Iterator<RocketComponent> iterator = clone.iterator(true);
while (iterator.hasNext()) {
iterator.next().newID();
}
return clone;
}
-
-
+
+
/**
* Make a deep copy of the rocket component tree structure from this component
* undo/redo mechanism. This method should not be used for other purposes,
* such as copy/paste. This method does not fire any events.
* <p>
- * This method must be overridden by any component that refers to mutable objects,
+ * This method must be overridden by any component that refers to mutable objects,
* or if some fields should not be copied. This should be performed by
* <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
* the appropriate fields.
* <p>
* This is not performed as serializing/deserializing for performance reasons.
- *
+ *
* @return A deep copy of the structure.
*/
protected RocketComponent copyWithOriginalID() {
} catch (CloneNotSupportedException e) {
throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
}
-
+
// Reset the mutex
clone.mutex = SafetyMutex.newInstance();
-
+
// Reset all parent/child information
clone.parent = null;
clone.children = new ArrayList<RocketComponent>();
-
+
// Add copied children to the structure without firing events.
for (RocketComponent child : this.children) {
RocketComponent childCopy = child.copyWithOriginalID();
clone.children.add(childCopy);
childCopy.parent = clone;
}
-
+
this.checkComponentStructure();
clone.checkComponentStructure();
-
+
return clone;
} finally {
mutex.unlock("copyWithOriginalID");
}
}
-
-
- /**
- * Accept a visitor to this RocketComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
+
////////////// Methods that may not be overridden ////////////
-
+
////////// Common parameter setting/getting //////////
-
+
/**
* Return the color of the object to use in 2D figures, or <code>null</code>
* to use the default color.
mutex.verify();
return color;
}
-
+
/**
- * Set the color of the object to use in 2D figures.
+ * Set the color of the object to use in 2D figures.
*/
public final void setColor(Color c) {
if ((color == null && c == null) ||
(color != null && color.equals(c)))
return;
-
+
checkState();
this.color = c;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
public final LineStyle getLineStyle() {
mutex.verify();
return lineStyle;
}
-
+
public final void setLineStyle(LineStyle style) {
if (this.lineStyle == style)
return;
this.lineStyle = style;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Get the current override mass. The mass is not necessarily in use
* at the moment.
- *
+ *
* @return the override mass
*/
public final double getOverrideMass() {
mutex.verify();
return overrideMass;
}
-
+
/**
* Set the current override mass. The mass is not set to use by this
* method.
- *
+ *
* @param m the override mass
*/
public final void setOverrideMass(double m) {
if (massOverriden)
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return whether mass override is active for this component. This does NOT
* take into account whether a parent component is overriding the mass.
- *
+ *
* @return whether the mass is overridden
*/
public final boolean isMassOverridden() {
mutex.verify();
return massOverriden;
}
-
+
/**
* Set whether the mass is currently overridden.
- *
+ *
* @param o whether the mass is overridden
*/
public final void setMassOverridden(boolean o) {
massOverriden = o;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/**
* Return the current override CG. The CG is not necessarily overridden.
- *
+ *
* @return the override CG
*/
public final Coordinate getOverrideCG() {
mutex.verify();
return getComponentCG().setX(overrideCGX);
}
-
+
/**
* Return the x-coordinate of the current override CG.
- *
+ *
* @return the x-coordinate of the override CG.
*/
public final double getOverrideCGX() {
mutex.verify();
return overrideCGX;
}
-
+
/**
* Set the current override CG to (x,0,0).
- *
+ *
* @param x the x-coordinate of the override CG to set.
*/
public final void setOverrideCGX(double x) {
else
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
+
/**
* Return whether the CG is currently overridden.
- *
+ *
* @return whether the CG is overridden
*/
public final boolean isCGOverridden() {
mutex.verify();
return cgOverriden;
}
-
+
/**
* Set whether the CG is currently overridden.
- *
+ *
* @param o whether the CG is overridden
*/
public final void setCGOverridden(boolean o) {
cgOverriden = o;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/**
* Return whether the mass and/or CG override overrides all subcomponent values
* always or never overrides subcomponents. In this case the subclass should
* also override {@link #isOverrideSubcomponentsEnabled()} to return
* <code>false</code>.
- *
+ *
* @return whether the current mass and/or CG override overrides subcomponents as well.
*/
public boolean getOverrideSubcomponents() {
mutex.verify();
return overrideSubcomponents;
}
-
-
+
+
/**
* Set whether the mass and/or CG override overrides all subcomponent values
* as well. See {@link #getOverrideSubcomponents()} for details.
- *
+ *
* @param override whether the mass and/or CG override overrides all subcomponent.
*/
public void setOverrideSubcomponents(boolean override) {
overrideSubcomponents = override;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return whether the option to override all subcomponents is enabled or not.
* The default implementation returns <code>false</code> if neither mass nor
* <p>
* This method may be overridden if the setting of overriding subcomponents
* cannot be set.
- *
+ *
* @return whether the option to override subcomponents is currently enabled.
*/
public boolean isOverrideSubcomponentsEnabled() {
mutex.verify();
return isCGOverridden() || isMassOverridden();
}
-
-
+
+
/**
mutex.verify();
return name;
}
-
+
/**
* Set the user-defined name of the component. If name==null, sets the name to
* the default name, currently the component name.
this.name = name;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Return the comment of the component. The component may contain multiple lines
* using \n as a newline separator.
- *
+ *
* @return the comment of the component.
*/
public final String getComment() {
mutex.verify();
return comment;
}
-
+
/**
* Set the comment of the component.
- *
+ *
* @param comment the comment of the component.
*/
public final void setComment(String comment) {
this.comment = comment;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Returns the unique ID of the component.
- *
+ *
* @return the ID of the component.
*/
public final String getID() {
return id;
}
-
+
/**
* Generate a new ID for this component.
*/
mutex.verify();
this.id = UniqueID.uuid();
}
-
-
+
+
/**
* Get the characteristic length of the component, for example the length of a body tube
* of the length of the root chord of a fin. This is used in positioning the component
* relative to its parent.
- *
+ *
* If the length of a component is settable, the class must define the setter method
* itself.
*/
mutex.verify();
return length;
}
-
+
/**
* Get the positioning of the component relative to its parent component.
* This is one of the enums of {@link Position}. A setter method is not provided,
mutex.verify();
return relativePosition;
}
-
-
+
+
/**
* Set the positioning of the component relative to its parent component.
* The actual position of the component is maintained to the best ability.
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param position the relative positioning.
*/
protected void setRelativePosition(RocketComponent.Position position) {
if (this.relativePosition == position)
return;
checkState();
-
+
// Update position so as not to move the component
if (this.parent != null) {
double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
-
+
switch (position) {
case ABSOLUTE:
this.position = this.toAbsolute(Coordinate.NUL)[0].x;
break;
-
+
case TOP:
this.position = thisPos;
break;
-
+
case MIDDLE:
this.position = thisPos - (this.parent.length - this.length) / 2;
break;
-
+
case BOTTOM:
this.position = thisPos - (this.parent.length - this.length);
break;
-
+
default:
throw new BugException("Unknown position type: " + position);
}
}
-
+
this.relativePosition = position;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
/**
* Get the position value of the component. The exact meaning of the value is
* dependent on the current relative positioning.
- *
+ *
* @return the positional value.
*/
public final double getPositionValue() {
mutex.verify();
return position;
}
-
-
+
+
/**
* Set the position value of the component. The exact meaning of the value
* depends on the current relative positioning.
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param value the position value of the component.
*/
public void setPositionValue(double value) {
checkState();
this.position = value;
}
-
-
+
+
/////////// Coordinate changes ///////////
-
+
/**
* Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
*/
checkState();
return toRelative(c, null);
}
-
-
+
+
/**
- * Return coordinate <code>c</code> described in the coordinate system of
+ * Return coordinate <code>c</code> described in the coordinate system of
* <code>dest</code>. If <code>dest</code> is <code>null</code> returns
* absolute coordinates.
* <p>
* This method returns an array of coordinates, each of which represents a
* position of the coordinate in clustered cases. The array is guaranteed
- * to contain at least one element.
+ * to contain at least one element.
* <p>
* The current implementation does not support rotating components.
- *
+ *
* @param c Coordinate in the component's coordinate system.
* @param dest Destination component coordinate system.
* @return an array of coordinates describing <code>c</code> in coordinates
RocketComponent search = dest;
Coordinate[] array = new Coordinate[1];
array[0] = c;
-
+
RocketComponent component = this;
while ((component != search) && (component.parent != null)) {
-
+
array = component.shiftCoordinates(array);
-
+
switch (component.relativePosition) {
case TOP:
for (int i = 0; i < array.length; i++) {
array[i] = array[i].add(component.position, 0, 0);
}
break;
-
+
case MIDDLE:
for (int i = 0; i < array.length; i++) {
array[i] = array[i].add(component.position +
(component.parent.length - component.length) / 2, 0, 0);
}
break;
-
+
case BOTTOM:
for (int i = 0; i < array.length; i++) {
array[i] = array[i].add(component.position +
(component.parent.length - component.length), 0, 0);
}
break;
-
+
case AFTER:
// Add length of all previous brother-components with POSITION_RELATIVE_AFTER
int index = component.parent.children.indexOf(component);
array[i] = array[i].add(component.position + component.parent.length, 0, 0);
}
break;
-
+
case ABSOLUTE:
search = null; // Requires back-search if dest!=null
if (Double.isNaN(absoluteX)) {
absoluteX = component.position;
}
break;
-
+
default:
throw new BugException("Unknown relative positioning type of component" +
component + ": " + component.relativePosition);
}
-
+
component = component.parent; // parent != null
}
-
+
if (!Double.isNaN(absoluteX)) {
for (int i = 0; i < array.length; i++) {
array[i] = array[i].setX(absoluteX + c.x);
}
}
-
+
// Check whether destination has been found or whether to backtrack
- // TODO: LOW: Backtracking into clustered components uses only one component
+ // TODO: LOW: Backtracking into clustered components uses only one component
if ((dest != null) && (component != dest)) {
Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
for (int i = 0; i < array.length; i++) {
array[i] = array[i].sub(origin[0]);
}
}
-
+
return array;
} finally {
mutex.unlock("toRelative");
}
}
-
-
+
+
/**
- * Recursively sum the lengths of all subcomponents that have position
+ * Recursively sum the lengths of all subcomponents that have position
* Position.AFTER.
- *
+ *
* @return Sum of the lengths.
*/
private final double getTotalLength() {
mutex.unlock("getTotalLength");
}
}
-
-
+
+
/////////// Total mass and CG calculation ////////////
-
+
/**
* Return the (possibly overridden) mass of component.
- *
+ *
* @return The mass of the component or the given override mass.
*/
public final double getMass() {
return overrideMass;
return getComponentMass();
}
-
+
/**
* Return the (possibly overridden) center of gravity and mass.
- *
+ *
* Returns the CG with the weight of the coordinate set to the weight of the component.
* Both CG and mass may be separately overridden.
- *
+ *
* @return The CG of the component or the given override CG.
*/
public final Coordinate getCG() {
checkState();
if (cgOverriden)
return getOverrideCG().setWeight(getMass());
-
+
if (massOverriden)
return getComponentCG().setWeight(getMass());
-
+
return getComponentCG();
}
-
-
+
+
/**
* Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
* The moment of inertia is scaled in reference to the (possibly overridden) mass
* and is relative to the non-overridden CG.
- *
+ *
* @return the longitudinal moment of inertia of this component.
*/
public final double getLongitudinalInertia() {
checkState();
return getLongitudinalUnitInertia() * getMass();
}
-
+
/**
* Return the rotational (around the y- or z-axis) moment of inertia of this component.
* The moment of inertia is scaled in reference to the (possibly overridden) mass
* and is relative to the non-overridden CG.
- *
+ *
* @return the rotational moment of inertia of this component.
*/
public final double getRotationalInertia() {
checkState();
return getRotationalUnitInertia() * getMass();
}
-
-
+
+
/////////// Children handling ///////////
-
+
/**
* Adds a child to the rocket component tree. The component is added to the end
- * of the component's child list. This is a helper method that calls
+ * of the component's child list. This is a helper method that calls
* {@link #addChild(RocketComponent,int)}.
- *
+ *
* @param component The component to add.
- * @throws IllegalArgumentException if the component is already part of some
+ * @throws IllegalArgumentException if the component is already part of some
* component tree.
* @see #addChild(RocketComponent,int)
*/
checkState();
addChild(component, children.size());
}
-
-
+
+
/**
- * Adds a child to the rocket component tree. The component is added to
+ * Adds a child to the rocket component tree. The component is added to
* the given position of the component's child list.
* <p>
- * This method may be overridden to enforce more strict component addition rules.
+ * This method may be overridden to enforce more strict component addition rules.
* The tests should be performed first and then this method called.
- *
+ *
* @param component The component to add.
* @param index Position to add component to.
- * @throws IllegalArgumentException If the component is already part of
+ * @throws IllegalArgumentException If the component is already part of
* some component tree.
*/
public void addChild(RocketComponent component, int index) {
throw new IllegalStateException("Component " + component.getComponentName() +
" not currently compatible with component " + getComponentName());
}
-
+
children.add(index, component);
component.parent = this;
-
+
this.checkComponentStructure();
component.checkComponentStructure();
-
+
fireAddRemoveEvent(component);
}
-
-
+
+
/**
* Removes a child from the rocket component tree.
- *
+ *
* @param n remove the n'th child.
* @throws IndexOutOfBoundsException if n is out of bounds
*/
checkState();
RocketComponent component = children.remove(n);
component.parent = null;
-
+
this.checkComponentStructure();
component.checkComponentStructure();
-
+
fireAddRemoveEvent(component);
}
-
+
/**
* Removes a child from the rocket component tree. Does nothing if the component
* is not present as a child.
- *
+ *
* @param component the component to remove
* @return whether the component was a child
*/
public final boolean removeChild(RocketComponent component) {
checkState();
-
+
component.checkComponentStructure();
-
+
if (children.remove(component)) {
component.parent = null;
-
+
this.checkComponentStructure();
component.checkComponentStructure();
-
+
fireAddRemoveEvent(component);
return true;
}
return false;
}
-
-
+
+
/**
* Move a child to another position.
- *
+ *
* @param component the component to move
* @param index the component's new position
* @throws IllegalArgumentException If an illegal placement was attempted.
checkState();
if (children.remove(component)) {
children.add(index, component);
-
+
this.checkComponentStructure();
component.checkComponentStructure();
-
+
fireAddRemoveEvent(component);
}
}
-
-
+
+
/**
* Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
* type of component removed.
if (c.isMassive())
type |= ComponentChangeEvent.MASS_CHANGE;
}
-
+
fireComponentChangeEvent(type);
}
-
-
+
+
public final int getChildCount() {
checkState();
this.checkComponentStructure();
return children.size();
}
-
+
public final RocketComponent getChild(int n) {
checkState();
this.checkComponentStructure();
return children.get(n);
}
-
+
public final List<RocketComponent> getChildren() {
checkState();
this.checkComponentStructure();
return children.clone();
}
-
-
+
+
/**
* Returns the position of the child in this components child list, or -1 if the
* component is not a child of this component.
- *
+ *
* @param child The child to search for.
* @return Position in the list or -1 if not found.
*/
this.checkComponentStructure();
return children.indexOf(child);
}
-
+
/**
* Get the parent component of this component. Returns <code>null</code> if the component
* has no parent.
- *
+ *
* @return The parent of this component or <code>null</code>.
*/
public final RocketComponent getParent() {
checkState();
return parent;
}
-
+
/**
* Get the root component of the component tree.
- *
+ *
* @return The root component of the component tree.
*/
public final RocketComponent getRoot() {
gp = gp.parent;
return gp;
}
-
+
/**
- * Returns the root Rocket component of this component tree. Throws an
+ * Returns the root Rocket component of this component tree. Throws an
* IllegalStateException if the root component is not a Rocket.
- *
+ *
* @return The root Rocket component of the component tree.
* @throws IllegalStateException If the root component is not a Rocket.
*/
throw new IllegalStateException("getRocket() called with root component "
+ r.getComponentName());
}
-
-
+
+
/**
* Return the Stage component that this component belongs to. Throws an
* IllegalStateException if a Stage is not in the parentage of this component.
- *
+ *
* @return The Stage component this component belongs to.
* @throws IllegalStateException if a Stage component is not in the parentage.
*/
}
throw new IllegalStateException("getStage() called without Stage as a parent.");
}
-
+
/**
* Return the stage number of the stage this component belongs to. The stages
* are numbered from zero upwards.
- *
+ *
* @return the stage number this component belongs to.
*/
public final int getStageNumber() {
if (parent == null) {
throw new IllegalArgumentException("getStageNumber() called for root component");
}
-
+
RocketComponent stage = this;
while (!(stage instanceof Stage)) {
stage = stage.parent;
}
return stage.parent.getChildPosition(stage);
}
-
-
+
+
/**
* Find a component with the given ID. The component tree is searched from this component
* down (including this component) for the ID and the corresponding component is returned,
* or null if not found.
- *
+ *
* @param idToFind ID to search for.
* @return The component with the ID, or null if not found.
*/
}
return null;
}
-
-
+
+
// TODO: Move these methods elsewhere (used only in SymmetricComponent)
public final RocketComponent getPreviousComponent() {
checkState();
c = c.getChild(c.getChildCount() - 1);
return c;
}
-
+
// TODO: Move these methods elsewhere (used only in SymmetricComponent)
public final RocketComponent getNextComponent() {
checkState();
if (getChildCount() > 0)
return getChild(0);
-
+
RocketComponent current = this;
RocketComponent nextParent = this.parent;
-
+
while (nextParent != null) {
int pos = nextParent.getChildPosition(current);
if (pos < nextParent.getChildCount() - 1)
return nextParent.getChild(pos + 1);
-
+
current = nextParent;
nextParent = current.parent;
}
return null;
}
-
-
+
+
/////////// Event handling //////////
//
// Listener lists are provided by the root Rocket component,
// a single listener list for the whole rocket.
//
-
+
/**
* Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
* component, which must be of type Rocket (which overrides this method). Events of all
* subcomponents are sent to all listeners.
- *
+ *
* @throws IllegalStateException - if the root component is not a Rocket
*/
public void addComponentChangeListener(ComponentChangeListener l) {
checkState();
getRocket().addComponentChangeListener(l);
}
-
+
/**
* Removes a ComponentChangeListener from the rocket tree. The listener is removed from
* the root component, which must be of type Rocket (which overrides this method).
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
public void removeComponentChangeListener(ComponentChangeListener l) {
getRoot().removeComponentChangeListener(l);
}
}
-
-
+
+
/**
- * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
- * <code>addComponentChangeListener()</code> except that it uses a
+ * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
+ * <code>addComponentChangeListener()</code> except that it uses a
* <code>ChangeListener</code>. The same events are dispatched to the
- * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
+ * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
* of <code>ChangeEvent</code>.
- *
+ *
* @throws IllegalStateException - if the root component is not a <code>Rocket</code>
*/
@Override
checkState();
getRocket().addChangeListener(l);
}
-
+
/**
* Removes a ChangeListener from the rocket tree. This is identical to
* removeComponentChangeListener() except it uses a ChangeListener.
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
@Override
getRoot().removeChangeListener(l);
}
}
-
-
+
+
/**
- * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
+ * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
* root component, which must be of type Rocket (which overrides this method).
* Events of all subcomponents are sent to all listeners.
- *
- * If the component tree root is not a Rocket, the event is ignored. This is the
- * case when constructing components not in any Rocket tree. In this case it
+ *
+ * If the component tree root is not a Rocket, the event is ignored. This is the
+ * case when constructing components not in any Rocket tree. In this case it
* would be impossible for the component to have listeners in any case.
- *
+ *
* @param e Event to send
*/
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
}
getRoot().fireComponentChangeEvent(e);
}
-
-
+
+
/**
* Fires a ComponentChangeEvent of the given type. The source of the event is set to
* this component.
- *
+ *
* @param type Type of event
* @see #fireComponentChangeEvent(ComponentChangeEvent)
*/
protected void fireComponentChangeEvent(int type) {
fireComponentChangeEvent(new ComponentChangeEvent(this, type));
}
-
-
+
+
/**
* Checks whether this component has been invalidated and should no longer be used.
* This is a safety check that in-place replaced components are no longer used.
* All non-trivial methods (with the exception of methods simply getting a property)
* should call this method before changing or computing anything.
- *
+ *
* @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
*/
protected void checkState() {
invalidator.check(true);
mutex.verify();
}
-
-
+
+
/**
* Check that the local component structure is correct. This can be called after changing
* the component structure in order to verify the integrity.
}
}
}
-
+
// Check whether the list contains exactly the searched-for component (with == operator)
private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
for (RocketComponent c : haystack) {
}
return false;
}
-
-
+
+
/////////// Iterators //////////
-
+
/**
* Returns an iterator that iterates over all children and sub-children.
* <p>
* <p>
* If an iterator iterating over only the direct children of the component is required,
* use <code>component.getChildren().iterator()</code>.
- *
+ *
* TODO: HIGH: Remove this after merges have been done
- *
- * @param returnSelf boolean value specifying whether the component itself should be
+ *
+ * @param returnSelf boolean value specifying whether the component itself should be
* returned
* @return An iterator for the children and sub-children.
* @deprecated Use {@link #iterator(boolean)} instead
public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
return iterator(returnSelf);
}
-
-
+
+
/**
* Returns an iterator that iterates over all children and sub-children, including itself.
* <p>
* This method is equivalent to <code>deepIterator(true)</code>.
- *
+ *
* TODO: HIGH: Remove this after merges have been done
- *
+ *
* @return An iterator for this component, its children and sub-children.
* @deprecated Use {@link #iterator()} instead
*/
public final Iterator<RocketComponent> deepIterator() {
return iterator();
}
-
-
+
+
/**
* Returns an iterator that iterates over all children and sub-children.
* <p>
* If an iterator iterating over only the direct children of the component is required,
* use <code>component.getChildren().iterator()</code>.
- *
- * @param returnSelf boolean value specifying whether the component itself should be
+ *
+ * @param returnSelf boolean value specifying whether the component itself should be
* returned
* @return An iterator for the children and sub-children.
*/
checkState();
return new RocketComponentIterator(this, returnSelf);
}
-
-
+
+
/**
* Returns an iterator that iterates over this components, its children and sub-children.
* <p>
* This method is equivalent to <code>iterator(true)</code>.
- *
+ *
* @return An iterator for this component, its children and sub-children.
*/
@Override
public final Iterator<RocketComponent> iterator() {
return iterator(true);
}
-
-
+
+
RocketComponent other = (RocketComponent) obj;
return this.id.equals(other.id);
}
-
-
+
+
@Override
public int hashCode() {
return id.hashCode();
}
-
-
+
+
//////////// Helper methods for subclasses
-
+
/**
* Helper method to add rotationally symmetric bounds at the specified coordinates.
* The X-axis value is <code>x</code> and the radius at the specified position is
- * <code>r</code>.
+ * <code>r</code>.
*/
protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
bounds.add(new Coordinate(x, -r, -r));
bounds.add(new Coordinate(x, r, r));
bounds.add(new Coordinate(x, -r, r));
}
-
-
+
+
protected static final Coordinate ringCG(double outerRadius, double innerRadius,
double x1, double x2, double density) {
return new Coordinate((x1 + x2) / 2, 0, 0,
ringMass(outerRadius, innerRadius, x2 - x1, density));
}
-
+
protected static final double ringMass(double outerRadius, double innerRadius,
double length, double density) {
return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
length * density;
}
-
+
protected static final double ringLongitudinalUnitInertia(double outerRadius,
double innerRadius, double length) {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
}
-
+
protected static final double ringRotationalUnitInertia(double outerRadius,
double innerRadius) {
// 1/2 * (r1^2 + r2^2)
return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
}
-
-
+
+
//////////// OTHER
-
+
/**
* Loads the RocketComponent fields from the given component. This method is meant
* to them have been removed (for example by firing appropriate events). The list contains
* all children and sub-children of the current component and the entire component
* tree of <code>src</code>.
- *
+ *
* @return a list of components that should not be used after this call.
*/
protected List<RocketComponent> copyFrom(RocketComponent src) {
checkState();
List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
-
+
if (this.parent != null) {
throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
this.parent.toDebugString() + ", this=" + this.toDebugString());
}
-
+
// Add current structure to be invalidated
Iterator<RocketComponent> iterator = this.iterator(false);
while (iterator.hasNext()) {
toInvalidate.add(iterator.next());
}
-
+
// Remove previous components
for (RocketComponent child : this.children) {
child.parent = null;
}
this.children.clear();
-
+
// Copy new children to this component
for (RocketComponent c : src.children) {
RocketComponent copy = c.copyWithOriginalID();
this.children.add(copy);
copy.parent = this;
}
-
+
this.checkComponentStructure();
src.checkComponentStructure();
-
+
// Set all parameters
this.length = src.length;
this.relativePosition = src.relativePosition;
this.name = src.name;
this.comment = src.comment;
this.id = src.id;
-
+
// Add source components to invalidation tree
for (RocketComponent c : src) {
toInvalidate.add(c);
}
-
+
return toInvalidate;
}
-
+
protected void invalidate() {
invalidator.invalidate();
}
-
-
+
+
////////// Iterator implementation ///////////
-
+
/**
* Private inner class to implement the Iterator.
- *
+ *
* This iterator is fail-fast if the root of the structure is a Rocket.
*/
private static class RocketComponentIterator implements Iterator<RocketComponent> {
// Stack holds iterators which still have some components left.
private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
-
+
private final Rocket root;
private final int treeModID;
-
+
private final RocketComponent original;
private boolean returnSelf = false;
-
+
// Construct iterator with component's child's iterator, if it has elements
public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
-
+
RocketComponent gp = c.getRoot();
if (gp instanceof Rocket) {
root = (Rocket) gp;
root = null;
treeModID = -1;
}
-
+
Iterator<RocketComponent> i = c.children.iterator();
if (i.hasNext())
iteratorStack.push(i);
-
+
this.original = c;
this.returnSelf = returnSelf;
}
-
+
@Override
public boolean hasNext() {
checkID();
return true;
return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
}
-
+
@Override
public RocketComponent next() {
Iterator<RocketComponent> i;
-
+
checkID();
-
+
// Return original component first
if (returnSelf) {
returnSelf = false;
return original;
}
-
+
// Peek first iterator from stack, throw exception if empty
i = iteratorStack.peek();
if (i == null) {
throw new NoSuchElementException("No further elements in RocketComponent iterator");
}
-
+
// Retrieve next component of the iterator, remove iterator from stack if empty
RocketComponent c = i.next();
if (!i.hasNext())
iteratorStack.pop();
-
+
// Add iterator of component children to stack if it has children
i = c.children.iterator();
if (i.hasNext())
iteratorStack.push(i);
-
+
return c;
}
-
+
private void checkID() {
if (root != null) {
if (root.getTreeModID() != treeModID) {
}
}
}
-
+
@Override
public void remove() {
throw new UnsupportedOperationException("remove() not supported by " +
"RocketComponent iterator");
}
}
-
+
}
package net.sf.openrocket.rocketcomponent;
public class Stage extends ComponentAssembly {
-
+
@Override
public String getComponentName () {
return "Stage";
}
-
-
+
+
@Override
public boolean allowsChildren() {
return true;
}
-
+
/**
- * Check whether the given type can be added to this component. A Stage allows
+ * Check whether the given type can be added to this component. A Stage allows
* only BodyComponents to be added.
*
* @param type The RocketComponent class type to add.
public boolean isCompatible (Class<? extends RocketComponent> type) {
return BodyComponent.class.isAssignableFrom(type);
}
-
- /**
- * Accept a visitor to this Stage in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Stage
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
public class Transition extends SymmetricComponent {
private static final double CLIP_PRECISION = 0.0001;
-
+
private Shape type;
private double shapeParameter;
private boolean clipped; // Not to be read - use isClipped(), which may be overriden
-
+
private double radius1, radius2;
private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic
-
+
private double foreShoulderRadius;
private double foreShoulderThickness;
private double aftShoulderThickness;
private double aftShoulderLength;
private boolean aftShoulderCapped;
-
+
// Used to cache the clip length
private double clipLength = -1;
-
+
public Transition() {
super();
-
+
this.radius1 = DEFAULT_RADIUS;
this.radius2 = DEFAULT_RADIUS;
this.length = DEFAULT_RADIUS * 3;
this.autoRadius1 = true;
this.autoRadius2 = true;
-
+
this.type = Shape.CONICAL;
this.shapeParameter = 0;
this.clipped = true;
}
-
-
+
+
//////// Fore radius ////////
-
+
@Override
public double getForeRadius() {
}
return radius1;
}
-
+
public void setForeRadius(double radius) {
if ((this.radius1 == radius) && (autoRadius1 == false))
return;
-
+
this.autoRadius1 = false;
this.radius1 = Math.max(radius, 0);
-
+
if (this.thickness > this.radius1 && this.thickness > this.radius2)
this.thickness = Math.max(this.radius1, this.radius2);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
@Override
public boolean isForeRadiusAutomatic() {
return autoRadius1;
}
-
+
public void setForeRadiusAutomatic(boolean auto) {
if (autoRadius1 == auto)
return;
-
+
autoRadius1 = auto;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
//////// Aft radius /////////
-
+
@Override
public double getAftRadius() {
if (isAftRadiusAutomatic()) {
}
return radius2;
}
-
-
+
+
public void setAftRadius(double radius) {
if ((this.radius2 == radius) && (autoRadius2 == false))
return;
-
+
this.autoRadius2 = false;
this.radius2 = Math.max(radius, 0);
-
+
if (this.thickness > this.radius1 && this.thickness > this.radius2)
this.thickness = Math.max(this.radius1, this.radius2);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
@Override
public boolean isAftRadiusAutomatic() {
return autoRadius2;
}
-
+
public void setAftRadiusAutomatic(boolean auto) {
if (autoRadius2 == auto)
return;
-
+
autoRadius2 = auto;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
//// Radius automatics
-
+
@Override
protected double getFrontAutoRadius() {
if (isAftRadiusAutomatic())
return -1;
return getAftRadius();
}
-
-
+
+
@Override
protected double getRearAutoRadius() {
if (isForeRadiusAutomatic())
return -1;
return getForeRadius();
}
-
-
+
+
//////// Type & shape /////////
-
+
public Shape getType() {
return type;
}
-
+
public void setType(Shape type) {
if (type == null) {
throw new IllegalArgumentException("setType called with null argument");
this.shapeParameter = type.defaultParameter();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getShapeParameter() {
return shapeParameter;
}
-
+
public void setShapeParameter(double n) {
if (shapeParameter == n)
return;
this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter());
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public boolean isClipped() {
if (!type.isClippable())
return false;
return clipped;
}
-
+
public void setClipped(boolean c) {
if (clipped == c)
return;
clipped = c;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public boolean isClippedEnabled() {
return type.isClippable();
}
-
+
public double getShapeParameterMin() {
return type.minParameter();
}
-
+
public double getShapeParameterMax() {
return type.maxParameter();
}
-
-
+
+
//////// Shoulders ////////
-
+
public double getForeShoulderRadius() {
return foreShoulderRadius;
}
-
+
public void setForeShoulderRadius(double foreShoulderRadius) {
if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
return;
this.foreShoulderRadius = foreShoulderRadius;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getForeShoulderThickness() {
return foreShoulderThickness;
}
-
+
public void setForeShoulderThickness(double foreShoulderThickness) {
if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness))
return;
this.foreShoulderThickness = foreShoulderThickness;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getForeShoulderLength() {
return foreShoulderLength;
}
-
+
public void setForeShoulderLength(double foreShoulderLength) {
if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength))
return;
this.foreShoulderLength = foreShoulderLength;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public boolean isForeShoulderCapped() {
return foreShoulderCapped;
}
-
+
public void setForeShoulderCapped(boolean capped) {
if (this.foreShoulderCapped == capped)
return;
this.foreShoulderCapped = capped;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public double getAftShoulderRadius() {
return aftShoulderRadius;
}
-
+
public void setAftShoulderRadius(double aftShoulderRadius) {
if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
return;
this.aftShoulderRadius = aftShoulderRadius;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getAftShoulderThickness() {
return aftShoulderThickness;
}
-
+
public void setAftShoulderThickness(double aftShoulderThickness) {
if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness))
return;
this.aftShoulderThickness = aftShoulderThickness;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getAftShoulderLength() {
return aftShoulderLength;
}
-
+
public void setAftShoulderLength(double aftShoulderLength) {
if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength))
return;
this.aftShoulderLength = aftShoulderLength;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public boolean isAftShoulderCapped() {
return aftShoulderCapped;
}
-
+
public void setAftShoulderCapped(boolean capped) {
if (this.aftShoulderCapped == capped)
return;
this.aftShoulderCapped = capped;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/////////// Shape implementations ////////////
-
+
/**
public double getRadius(double x) {
if (x < 0 || x > length)
return 0;
-
+
double r1 = getForeRadius();
double r2 = getAftRadius();
-
+
if (r1 == r2)
return r1;
-
+
if (r1 > r2) {
x = length - x;
double tmp = r1;
r1 = r2;
r2 = tmp;
}
-
+
if (isClipped()) {
// Check clip calculation
if (clipLength < 0)
return r1 + type.getRadius(x, r2 - r1, length, shapeParameter);
}
}
-
+
/**
* Numerically solve clipLength from the equation
* r1 == type.getRadius(clipLength,r2,clipLength+length)
*/
private void calculateClip(double r1, double r2) {
double min = 0, max = length;
-
+
if (r1 >= r2) {
double tmp = r1;
r1 = r2;
r2 = tmp;
}
-
+
if (r1 == 0) {
clipLength = 0;
return;
}
-
+
if (length <= 0) {
clipLength = 0;
return;
}
-
+
// Required:
// getR(min,min+length,r2) - r1 < 0
// getR(max,max+length,r2) - r1 > 0
-
+
int n = 0;
while (type.getRadius(max, r2, max + length, shapeParameter) - r1 < 0) {
min = max;
if (n > 10)
break;
}
-
+
while (true) {
clipLength = (min + max) / 2;
if ((max - min) < CLIP_PRECISION)
}
}
}
-
-
+
+
@Override
public double getInnerRadius(double x) {
return Math.max(getRadius(x) - thickness, 0);
}
-
-
+
+
@Override
public Collection<Coordinate> getComponentBounds() {
addBound(bounds, getLength() + aftShoulderLength, aftShoulderRadius);
return bounds;
}
-
+
@Override
public double getComponentMass() {
double mass = super.getComponentMass();
final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
mass += ringMass(ir, 0, getForeShoulderThickness(), getMaterial().getDensity());
}
-
+
if (getAftShoulderLength() > 0.001) {
final double or = getAftShoulderRadius();
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
mass += ringMass(ir, 0, getAftShoulderThickness(), getMaterial().getDensity());
}
-
+
return mass;
}
-
+
@Override
public Coordinate getComponentCG() {
Coordinate cg = super.getComponentCG();
getForeShoulderThickness() - getForeShoulderLength(),
getMaterial().getDensity()));
}
-
+
if (getAftShoulderLength() > 0.001) {
final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(),
}
return cg;
}
-
-
+
+
/*
* The moments of inertia are not explicitly corrected for the shoulders.
* However, since the mass is corrected, the inertia is automatically corrected
public String getComponentName() {
return "Transition";
}
-
+
@Override
protected void componentChanged(ComponentChangeEvent e) {
super.componentChanged(e);
clipLength = -1;
}
-
- /**
- * Accept a visitor to this Transition in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Transition
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
/**
* Check whether the given type can be added to this component. Transitions allow any
* InternalComponents to be added.
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
return true;
return false;
}
-
-
+
+
/**
* An enumeration listing the possible shapes of transitions.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public static enum Shape {
-
+
/**
* Conical shape.
*/
return radius * x / length;
}
},
-
+
/**
* Ogive shape. The shape parameter is the portion of an extended tangent ogive
* that will be used. That is, for param==1 a tangent ogive will be produced, and
public boolean usesParameter() {
return true; // Range 0...1 is default
}
-
+
@Override
public double defaultParameter() {
return 1.0; // Tangent ogive by default
}
-
+
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
assert radius >= 0;
assert param >= 0;
assert param <= 1;
-
+
// Impossible to calculate ogive for length < radius, scale instead
// TODO: LOW: secant ogive could be calculated lower
if (length < radius) {
x = x * radius / length;
length = radius;
}
-
+
if (param < 0.001)
return CONICAL.getRadius(x, radius, length, param);
-
+
// Radius of circle is:
double R = sqrt((pow2(length) + pow2(radius)) *
(pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
return sqrt(R * R - (L - x) * (L - x)) - y0;
}
},
-
+
/**
* Ellipsoidal shape.
*/
return sqrt(2 * radius * x - x * x); // radius/length * sphere
}
},
-
+
POWER("Power series",
"A power series nose cone has a profile of " +
"<i>Radius</i> × (<i>x</i> / <i>Length</i>)" +
public boolean usesParameter() { // Range 0...1
return true;
}
-
+
@Override
public double defaultParameter() {
return 0.5;
}
-
+
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
}
return radius * Math.pow(x / length, param);
}
-
+
},
-
+
PARABOLIC("Parabolic series",
"A parabolic series nose cone has a profile of a parabola. The shape " +
"parameter defines the segment of the parabola to utilize. The shape " +
"parameter 1.0 produces a <b>full parabola</b> which is tangent to the body " +
"tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a " +
"<b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.") {
-
+
// In principle a parabolic transition is clippable, but the difference is
// negligible.
-
+
@Override
public boolean usesParameter() { // Range 0...1
return true;
}
-
+
@Override
public double defaultParameter() {
return 1.0;
}
-
+
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
assert radius >= 0;
assert param >= 0;
assert param <= 1;
-
+
return radius * ((2 * x / length - param * pow2(x / length)) / (2 - param));
}
},
-
+
HAACK("Haack series",
public boolean usesParameter() {
return true;
}
-
+
@Override
public double maxParameter() {
return 1.0 / 3.0; // Range 0...1/3
}
-
+
@Override
public double getRadius(double x, double radius, double length, double param) {
assert x >= 0;
assert radius >= 0;
assert param >= 0;
assert param <= 2;
-
+
double theta = Math.acos(1 - 2 * x / length);
if (param == 0) {
return radius * sqrt((theta - sin(2 * theta) / 2) / Math.PI);
return radius * sqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
}
},
-
+
// POLYNOMIAL("Smooth polynomial",
// "A polynomial is fitted such that the nose cone profile is horizontal "+
// "at the aft end of the transition. The angle at the tip is defined by "+
// }
// }
;
-
+
// Privete fields of the shapes
private final String name;
private final String transitionDesc;
private final String noseconeDesc;
private final boolean canClip;
-
+
// Non-clippable constructor
Shape(String name, String noseconeDesc, String transitionDesc) {
this(name, noseconeDesc, transitionDesc, false);
}
-
+
// Clippable constructor
Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) {
this.name = name;
this.noseconeDesc = noseconeDesc;
this.transitionDesc = transitionDesc;
}
-
-
+
+
/**
* Return the name of the transition shape name.
*/
public String getName() {
return name;
}
-
+
/**
* Get a description of the Transition shape.
*/
public String getTransitionDescription() {
return transitionDesc;
}
-
+
/**
* Get a description of the NoseCone shape.
*/
public String getNoseConeDescription() {
return noseconeDesc;
}
-
+
/**
* Check whether the shape differs in clipped mode. The clipping should be
* enabled by default if possible.
public boolean isClippable() {
return canClip;
}
-
+
/**
* Return whether the shape uses the shape parameter. (Default false.)
*/
public boolean usesParameter() {
return false;
}
-
+
/**
* Return the minimum value of the shape parameter. (Default 0.)
*/
public double minParameter() {
return 0.0;
}
-
+
/**
* Return the maximum value of the shape parameter. (Default 1.)
*/
public double maxParameter() {
return 1.0;
}
-
+
/**
* Return the default value of the shape parameter. (Default 0.)
*/
public double defaultParameter() {
return 0.0;
}
-
+
/**
* Calculate the basic radius of a transition with the given radius, length and
* shape parameter at the point x from the tip of the component. It is assumed
* that the fore radius if zero and the aft radius is <code>radius >= 0</code>.
* Boattails are achieved by reversing the component.
- *
+ *
* @param x Position from the tip of the component.
* @param radius Aft end radius >= 0.
* @param length Length of the transition >= 0.
* @return The basic radius at the given position.
*/
public abstract double getRadius(double x, double radius, double length, double param);
-
-
+
+
/**
* Returns the name of the shape (same as getName()).
*/
/**
* A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket
* base line, while the leading and aft edges may be slanted.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
* sweep tipChord
* | |___________
* | / |
- * | / |
+ * | / |
* | / | height
* / |
* __________/________________|_____________
* length
* == rootChord
*/
-
+
// rootChord == length
private double tipChord = 0;
private double height = 0;
private double sweep = 0;
-
-
+
+
public TrapezoidFinSet() {
this (3, 0.05, 0.05, 0.025, 0.05);
}
-
+
// TODO: HIGH: height=0 -> CP = NaN
- public TrapezoidFinSet(int fins, double rootChord, double tipChord, double sweep,
+ public TrapezoidFinSet(int fins, double rootChord, double tipChord, double sweep,
double height) {
super();
-
+
this.setFinCount(fins);
this.length = rootChord;
this.tipChord = tipChord;
this.sweep = sweep;
this.height = height;
}
-
-
+
+
public void setFinShape(double rootChord, double tipChord, double sweep, double height,
double thickness) {
if (this.length==rootChord && this.tipChord==tipChord && this.sweep==sweep &&
length = Math.max(r,0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getTipChord() {
return tipChord;
}
sweep = r;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
/**
* Get the sweep angle. This is calculated from the true sweep and height, and is not
* stored separetely.
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
/**
* Returns the geometry of a trapezoidal fin.
@Override
public Coordinate[] getFinPoints() {
Coordinate[] c = new Coordinate[4];
-
+
c[0] = Coordinate.NUL;
c[1] = new Coordinate(sweep,height);
c[2] = new Coordinate(sweep+tipChord,height);
c[3] = new Coordinate(length,0);
-
+
return c;
}
-
+
/**
* Returns the span of a trapezoidal fin.
*/
public double getSpan() {
return height;
}
-
+
@Override
public String getComponentName() {
return "Trapezoidal fin set";
}
- /**
- * Accept a visitor to this TrapezoidFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this TrapezoidFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}