+2012-03-14 Jason Blood
+
+ * Importing images to freeform fin sets
+
2012-03-13 Sampo Niskanne
* [BUG] Threads piled up when running simulations
RocketPanel.FigTypeAct.ttip.Sideview = Side view
RocketPanel.FigTypeAct.Backview = Back view
RocketPanel.FigTypeAct.ttip.Backview = Rear view
+RocketPanel.FigViewAct.2D = 2D View
+RocketPanel.FigViewAct.ttip.2D = 2D View
+RocketPanel.FigViewAct.3D = 3D View
+RocketPanel.FigViewAct.ttip.3D = 3D View
RocketPanel.lbl.Motorcfg = Motor configuration:
RocketPanel.lbl.infoMessage = <html>Click to select Shift+click to select other Double-click to edit Click+drag to move
FreeformFinSetConfig.lbl.doubleClick2 = to edit
FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point
+FreeformFinSetConfig.lbl.scaleFin = Scale Fin
!InnerTubeConfig
GuidedTourSelectionDialog.btn.start = Start tour!
+! Custom Fin BMP Importer
+CustomFinImport.button.label = Import from BMP
+CustomFinImport.filter = Bitmap Files (*.bmp)
+CustomFinImport.badFinImage = Invalid fin image. Must be a black and white image (black for the fin), not touching any side, except the bottom of the image, which is the base of the fin.
+CustomFinImport.errorLoadingFile = Error loading file:
+CustomFinImport.errorParsingFile = Error parsing fin image:
+CustomFinImport.undo = Import freeform fin set
+CustomFinImport.error.title = Error loading fin profile
+CustomFinImport.error.badimage = Could not deduce fin shape from image.
+
import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.List;
+import javax.swing.JButton;
import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.dialogs.ScaleDialog;
import net.sf.openrocket.gui.scalefigure.FinPointFigure;
import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
import net.sf.openrocket.gui.scalefigure.ScaleSelector;
+import net.sf.openrocket.gui.util.CustomFinImporter;
+import net.sf.openrocket.gui.util.FileHelper;
+import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
this.finset = (FreeformFinSet) component;
//// General and General properties
- tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(),
- trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
+ tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(), trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
//// Shape and Fin shape
- tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(),
- trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
+ tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(), trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
tabbedPane.setSelectedIndex(0);
addFinSetButtons();
}
-
+
private JPanel generalPane() {
DoubleModel m;
JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel", "[][65lp::][30lp::]", ""));
-
-
+
+
//// Number of fins:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Numberoffins")));
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx, wrap");
-
+
//// Base rotation
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Finrotation")));
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)), "w 100lp, wrap");
-
-
+
+
//// Fin cant
JLabel label = new JLabel(trans.get("FreeformFinSetCfg.lbl.Fincant"));
//// The angle that the fins are canted with respect to the rocket body.
label.setToolTipText(trans.get("FreeformFinSetCfg.lbl.ttip.Fincant"));
panel.add(label);
- m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE,
- -FinSet.MAX_CANT, FinSet.MAX_CANT);
+ m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, -FinSet.MAX_CANT, FinSet.MAX_CANT);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
- panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)),
- "w 100lp, wrap 40lp");
+ panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), "w 100lp, wrap 40lp");
+
+
-
-
//// Position
//// Position relative to:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto")));
- combo = new JComboBox(
- new EnumModel<RocketComponent.Position>(component, "RelativePosition",
- new RocketComponent.Position[] {
- RocketComponent.Position.TOP,
- RocketComponent.Position.MIDDLE,
- RocketComponent.Position.BOTTOM,
- RocketComponent.Position.ABSOLUTE
- }));
+ combo = new JComboBox(new EnumModel<RocketComponent.Position>(component, "RelativePosition", new RocketComponent.Position[] { RocketComponent.Position.TOP, RocketComponent.Position.MIDDLE,
+ RocketComponent.Position.BOTTOM, RocketComponent.Position.ABSOLUTE }));
panel.add(combo, "spanx 3, growx, wrap");
//// plus
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right");
panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx");
- panel.add(new BasicSlider(m.getSliderModel(
- new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE),
- new DoubleModel(component.getParent(), "Length"))),
- "w 100lp, wrap");
+ panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), new DoubleModel(component.getParent(), "Length"))), "w 100lp, wrap");
+
+
+
+
-
-
-
-
mainPanel.add(panel, "aligny 20%");
mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp");
-
+
panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
-
-
-
+
+
+
//// Cross section
//// Fin cross section:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split");
- combo = new JComboBox(
- new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
+ combo = new JComboBox(new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
panel.add(combo, "growx, wrap unrel");
-
+
//// Thickness:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Thickness")));
panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap 30lp");
-
+
//// Material
materialPanel(panel, Material.Type.BULK);
-
-
+
+
mainPanel.add(panel, "aligny 20%");
return mainPanel;
}
-
+
private JPanel shapePane() {
JPanel panel = new JPanel(new MigLayout("fill"));
-
+
// Create the figure
figure = new FinPointFigure(finset);
ScaleScrollPane figurePane = new FinPointScrollPane();
table = new JTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
for (int i = 0; i < Columns.values().length; i++) {
- table.getColumnModel().getColumn(i).
- setPreferredWidth(Columns.values()[i].getWidth());
+ table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth());
}
JScrollPane tablePane = new JScrollPane(table);
-
+ JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin"));
+ scaleButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Scaling free-form fin");
+ ScaleDialog dialog = new ScaleDialog(document, finset, SwingUtilities.getWindowAncestor(FreeformFinSetConfig.this), true);
+ dialog.setVisible(true);
+ dialog.dispose();
+ }
+ });
+
// panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%");
// panel.add(new JLabel(" View:"), "wrap, aligny bottom");
-
+
panel.add(tablePane, "growy, width 100lp:100lp:, height 100lp:250lp:");
- panel.add(figurePane, "gap unrel, spanx, growx, growy 1000, height 100lp:250lp:, wrap");
+ panel.add(figurePane, "gap unrel, spanx, spany 3, growx, growy 1000, height 100lp:250lp:, wrap");
- panel.add(new StyledLabel(trans.get("lbl.doubleClick1"), -2), "alignx 50%");
+ panel.add(new StyledLabel(trans.get("lbl.doubleClick1"), -2), "alignx 50%, wrap");
+ panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%, wrap");
- panel.add(new ScaleSelector(figurePane), "spany 2");
- panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag") + " " +
- trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spany 2, right, wrap");
+ panel.add(scaleButton, "spany 2, alignx 50%, aligny 50%");
+ panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%");
-
- panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%");
+ JButton importButton = new JButton(trans.get("CustomFinImport.button.label"));
+ importButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ importImage();
+ }
+ });
+ panel.add(importButton, "spany 2, bottom");
+
+ // panel.add(new CustomFinBmpImporter(finset), "spany 2, bottom");
+ panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "right, wrap");
+ panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "right");
return panel;
}
-
-
-
+
+
+
+ private void importImage() {
+ JFileChooser chooser = new JFileChooser();
+
+ chooser.addChoosableFileFilter(FileHelper.BMP_FILE_FILTER);
+ chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
+ int option = chooser.showOpenDialog(this);
+
+
+ if (option == JFileChooser.APPROVE_OPTION) {
+ try {
+ CustomFinImporter importer = new CustomFinImporter();
+ List<Coordinate> points = importer.getPoints(chooser.getSelectedFile());
+ document.startUndo(trans.get("CustomFinImport.undo"));
+ finset.setPoints(points);
+ } catch (IllegalFinPointException e) {
+ log.warn("Error storing fin points", e);
+ JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"),
+ trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
+ } catch (IOException e) {
+ log.warn("Error loading file", e);
+ JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
+ trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
+ } finally {
+ document.stopUndo();
+ }
+ }
+
+ }
+
+
+
@Override
public void updateFields() {
super.updateFields();
}
-
-
+
+
private class FinPointScrollPane extends ScaleScrollPane {
- private static final int ANY_MASK =
- (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK |
- MouseEvent.META_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK |
- MouseEvent.SHIFT_DOWN_MASK);
+ private static final int ANY_MASK = (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK | MouseEvent.META_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK);
private int dragIndex = -1;
public void mousePressed(MouseEvent event) {
int mods = event.getModifiersEx();
- if (event.getButton() != MouseEvent.BUTTON1 ||
- (mods & ANY_MASK) != 0) {
+ if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) {
super.mousePressed(event);
return;
}
@Override
public void mouseDragged(MouseEvent event) {
int mods = event.getModifiersEx();
- if (dragIndex < 0 ||
- (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) !=
- MouseEvent.BUTTON1_DOWN_MASK) {
+ if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
super.mouseDragged(event);
return;
}
try {
finset.setPoint(dragIndex, point.x, point.y);
} catch (IllegalFinPointException ignore) {
- log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex +
- " x=" + point.x + " y=" + point.y);
+ log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
}
}
@Override
public void mouseClicked(MouseEvent event) {
int mods = event.getModifiersEx();
- if (event.getButton() != MouseEvent.BUTTON1 ||
- (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
+ if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
super.mouseClicked(event);
return;
}
return figure.convertPoint(x, y);
}
-
+
}
-
-
-
+
+
+
private enum Columns {
// NUMBER {
// @Override
@Override
public String getValue(FreeformFinSet finset, int row) {
- return UnitGroup.UNITS_LENGTH.getDefaultUnit()
- .toString(finset.getFinPoints()[row].x);
+ return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].x);
}
},
Y {
@Override
public String getValue(FreeformFinSet finset, int row) {
- return UnitGroup.UNITS_LENGTH.getDefaultUnit()
- .toString(finset.getFinPoints()[row].y);
+ return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].y);
}
};
if (!(o instanceof String))
return;
- if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length ||
- columnIndex < 0 || columnIndex >= Columns.values().length) {
- throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex +
- " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
+ if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || columnIndex < 0 || columnIndex >= Columns.values().length) {
+ throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex + " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
}
String str = (String) o;
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
/*
* Scaler implementations
*
}
-
-
-
+
+
+
private static final double DEFAULT_INITIAL_SIZE = 0.1; // meters
private static final double SCALE_MIN = 0.01;
private static final double SCALE_MAX = 100.0;
private static final String SCALE_SUBSELECTION = trans.get("lbl.scaleSubselection");
private static final String SCALE_SELECTION = trans.get("lbl.scaleSelection");
-
-
-
+
+
+
private final DoubleModel multiplier = new DoubleModel(1.0, UnitGroup.UNITS_RELATIVE, SCALE_MIN, SCALE_MAX);
private final DoubleModel fromField = new DoubleModel(0, UnitGroup.UNITS_LENGTH, 0);
private final DoubleModel toField = new DoubleModel(0, UnitGroup.UNITS_LENGTH, 0);
private final OpenRocketDocument document;
private final RocketComponent selection;
+ private final boolean onlySelection;
- private final JComboBox selectionOption;
- private final JCheckBox scaleMassValues;
+ private JComboBox selectionOption;
+ private JCheckBox scaleMassValues;
private boolean changing = false;
* @param parent the parent window.
*/
public ScaleDialog(OpenRocketDocument document, RocketComponent selection, Window parent) {
+ this(document, selection, parent, false);
+ }
+
+ /**
+ * Sole constructor.
+ *
+ * @param document the document to modify.
+ * @param selection the currently selected component (or <code>null</code> if none selected).
+ * @param parent the parent window.
+ * @param onlySelection true to only allow scaling on the selected component (not the whole rocket)
+ */
+ public ScaleDialog(OpenRocketDocument document, RocketComponent selection, Window parent, Boolean onlySelection) {
super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
this.document = document;
this.selection = selection;
+ this.onlySelection = onlySelection;
+ init();
+ }
+
+ private void init() {
// Generate options for scaling
List<String> options = new ArrayList<String>();
- options.add(SCALE_ROCKET);
+ if (!onlySelection)
+ options.add(SCALE_ROCKET);
if (selection != null && selection.getChildCount() > 0) {
options.add(SCALE_SUBSELECTION);
}
options.add(SCALE_SELECTION);
}
-
+
/*
* Select initial size for "from" field.
*
fromField.setValue(initialSize);
toField.setValue(initialSize);
-
+
// Add actions to the values
multiplier.addChangeListener(new ChangeListener() {
@Override
}
});
-
-
+
+
String tip;
JPanel panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::][]", ""));
this.add(panel);
-
+
// Scaling selection
tip = trans.get("lbl.scale.ttip");
JLabel label = new JLabel(trans.get("lbl.scale"));
selectionOption.setToolTipText(tip);
panel.add(selectionOption, "growx, wrap para*2");
-
+
// Scale multiplier
tip = trans.get("lbl.scaling.ttip");
label = new JLabel(trans.get("lbl.scaling"));
label.setToolTipText(tip);
panel.add(label, "gapright unrel");
-
+
JSpinner spin = new JSpinner(multiplier.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
spin.setToolTipText(tip);
slider.setToolTipText(tip);
panel.add(slider, "w 100lp, growx, wrap para");
-
+
// Scale from ... to ...
tip = trans.get("lbl.scaleFromTo.ttip");
label = new JLabel(trans.get("lbl.scaleFrom"));
unit.setToolTipText(tip);
panel.add(unit, "w 30lp, wrap para*2");
-
+
// Scale override
scaleMassValues = new JCheckBox(trans.get("checkbox.scaleMass"));
scaleMassValues.setToolTipText(trans.get("checkbox.scaleMass.ttip"));
scaleMassValues.setEnabled(overridden);
panel.add(scaleMassValues, "span, wrap para*3");
-
+
// Buttons
JButton scale = new JButton(trans.get("button.scale"));
});
panel.add(cancel, "right, gap para");
-
-
+
+
GUIUtil.setDisposableDialogOptions(this, scale);
}
-
+
private void doScale() {
double mul = multiplier.getValue();
if (!(SCALE_MIN <= mul && mul <= SCALE_MAX)) {
}
-
+
/**
* Interface for scaling a specific component/value.
*/
+
/**
* Sole constructor. Creates a new frame based on the supplied document
* and adds it to the current frames list.
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
-import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public static final int MINOR_TICKS = 3;
public static final int MAJOR_TICKS = 30;
-
+
private JComponent component;
private ScaleFigure figure;
- private JViewport viewport;
private DoubleModel rulerUnit;
private Ruler horizontalRuler;
this.figure = (ScaleFigure) component;
this.allowFit = allowFit;
-
+
rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
rulerUnit.addChangeListener(new ChangeListener() {
@Override
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
-
- viewport = this.getViewport();
+
viewport.addMouseListener(this);
viewport.addMouseMotionListener(this);
}
-
+
public double getScaling() {
return figure.getScaling();
}
//////////////// Mouse handlers ////////////////
-
+
private int dragStartX = 0;
private int dragStartY = 0;
private Rectangle dragRectangle = null;
}
-
+
//////////////// The view port rulers ////////////////
-
+
private class Ruler extends JComponent {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
g2.setColor(getBackground());
g2.fillRect(area.x, area.y, area.width, area.height + 100);
-
+
int startpx, endpx;
if (orientation == HORIZONTAL) {
startpx = area.x;
Tick[] ticks = unit.getTicks(start, end, minor, major);
-
+
// Set color & hints
g2.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
--- /dev/null
+package net.sf.openrocket.gui.util;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.imageio.ImageIO;
+
+import net.sf.openrocket.l10n.LocalizedIOException;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.Coordinate;
+
+public class CustomFinImporter {
+
+ private enum FacingDirections {
+ UP, DOWN, LEFT, RIGHT
+ }
+
+ private int startX;
+ private FacingDirections facing;
+ private int currentX, currentY;
+
+
+
+ public List<Coordinate> getPoints(File file) throws IOException {
+ ArrayList<Coordinate> points = new ArrayList<Coordinate>();
+
+ BufferedImage pic = ImageIO.read(file);
+
+ // Set initial values for parsing
+ startX = -1;
+ facing = FacingDirections.UP;
+
+ if (validateImage(pic)) {
+ points.add(Coordinate.NUL);
+ loadFin(pic, points);
+ } else {
+ throw new LocalizedIOException("CustomFinImport.error.badimage");
+ }
+
+ optimizePoints(points);
+ return points;
+ }
+
+
+ private boolean validateImage(BufferedImage pic) {
+ int height = pic.getHeight();
+ int width = pic.getWidth();
+ Boolean bottomEdgeFound = false;
+
+ for (int x = 0; x < width; ++x) {
+ for (int y = 0; y < height; ++y) {
+ int pixel = pic.getRGB(x, y) & 0x00FFFFFF; // Clear alpha, we don't care about it
+ if ((pixel == 0xFFFFFF) || (pixel == 0)) // black or white only
+ {
+ if ((x == 0) || (x == width - 1) || (y == 0)) {
+ // Left, right and top must have no black (fin)
+ if (pixel == 0)
+ return false;
+ } else if (y == height - 1) {
+ if (pixel == 0) {
+ bottomEdgeFound = true;
+ if (startX == -1)
+ startX = x;
+ }
+ }
+ } else {
+ // Found something other than a black or white pixel
+ return false;
+ }
+ }
+ }
+ return bottomEdgeFound;
+ }
+
+ private void loadFin(BufferedImage pic, ArrayList<Coordinate> points) {
+ boolean calledTurnedAround = false;
+ int height = pic.getHeight();
+
+ currentX = startX;
+ currentY = pic.getHeight() - 1;
+
+ do {
+ if (CheckLeftIsFin(pic, currentX, currentY))
+ RotateLeft();
+ else if (CheckForwardIsFin(pic, currentX, currentY)) {
+ // Do nothing
+ } else if (CheckRightIsFin(pic, currentX, currentY))
+ RotateRight();
+ else {
+ TurnAround();
+ calledTurnedAround = true;
+ }
+
+ MoveForward(pic);
+ if (pixelIsFin(pic, currentX, currentY)) {
+ if (!calledTurnedAround) {
+ double x = (currentX - startX) * 0.001;
+ double y = (height - currentY - 1) * 0.001;
+ points.add(new Coordinate(x, y));
+ } else
+ calledTurnedAround = false;
+ }
+ } while (currentY < height - 1 && currentY >= 0);
+ }
+
+ private boolean pixelIsFin(BufferedImage pic, int x, int y) {
+ int height = pic.getHeight();
+ int width = pic.getWidth();
+
+ if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
+ int pixel = pic.getRGB(x, y) & 0x00FFFFFF; // Clear alpha, we don't care about it
+
+ if (pixel == 0) // black is fin
+ return true;
+ }
+ return false;
+ }
+
+ private boolean CheckLeftIsFin(BufferedImage pic, int x, int y) {
+ if (facing == FacingDirections.DOWN)
+ return pixelIsFin(pic, x + 1, y);
+ else if (facing == FacingDirections.UP)
+ return pixelIsFin(pic, x - 1, y);
+ else if (facing == FacingDirections.LEFT)
+ return pixelIsFin(pic, x, y + 1);
+ else if (facing == FacingDirections.RIGHT)
+ return pixelIsFin(pic, x, y - 1);
+ else
+ return false;
+ }
+
+ private Boolean CheckRightIsFin(BufferedImage pic, int x, int y) {
+ if (facing == FacingDirections.DOWN)
+ return pixelIsFin(pic, x - 1, y);
+ else if (facing == FacingDirections.UP)
+ return pixelIsFin(pic, x + 1, y);
+ else if (facing == FacingDirections.LEFT)
+ return pixelIsFin(pic, x, y - 1);
+ else if (facing == FacingDirections.RIGHT)
+ return pixelIsFin(pic, x, y + 1);
+ else
+ return false;
+ }
+
+ private boolean CheckForwardIsFin(BufferedImage pic, int x, int y) {
+ if (facing == FacingDirections.DOWN)
+ return pixelIsFin(pic, x, y + 1);
+ else if (facing == FacingDirections.UP)
+ return pixelIsFin(pic, x, y - 1);
+ else if (facing == FacingDirections.LEFT)
+ return pixelIsFin(pic, x - 1, y);
+ else if (facing == FacingDirections.RIGHT)
+ return pixelIsFin(pic, x + 1, y);
+ else
+ return false;
+ }
+
+ private void RotateLeft() {
+ if (facing == FacingDirections.UP)
+ facing = FacingDirections.LEFT;
+ else if (facing == FacingDirections.RIGHT)
+ facing = FacingDirections.UP;
+ else if (facing == FacingDirections.DOWN)
+ facing = FacingDirections.RIGHT;
+ else if (facing == FacingDirections.LEFT)
+ facing = FacingDirections.DOWN;
+ }
+
+ private void RotateRight() {
+ if (facing == FacingDirections.UP)
+ facing = FacingDirections.RIGHT;
+ else if (facing == FacingDirections.RIGHT)
+ facing = FacingDirections.DOWN;
+ else if (facing == FacingDirections.DOWN)
+ facing = FacingDirections.LEFT;
+ else if (facing == FacingDirections.LEFT)
+ facing = FacingDirections.UP;
+ }
+
+ private void MoveForward(BufferedImage pic) {
+ if (facing == FacingDirections.UP) {
+ if (currentY > 0)
+ currentY--;
+ } else if (facing == FacingDirections.RIGHT) {
+ if (currentX < pic.getWidth() - 1)
+ currentX++;
+ } else if (facing == FacingDirections.DOWN) {
+ if (currentY < pic.getHeight() - 1)
+ currentY++;
+ } else if (facing == FacingDirections.LEFT) {
+ if (currentX > 0)
+ currentX--;
+ }
+ }
+
+ private void TurnAround() {
+ if (facing == FacingDirections.UP)
+ facing = FacingDirections.DOWN;
+ else if (facing == FacingDirections.DOWN)
+ facing = FacingDirections.UP;
+ else if (facing == FacingDirections.RIGHT)
+ facing = FacingDirections.LEFT;
+ else if (facing == FacingDirections.LEFT)
+ facing = FacingDirections.RIGHT;
+ }
+
+ private void optimizePoints(ArrayList<Coordinate> points) {
+ int startIx;
+ ListIterator<Coordinate> start, entry, entry2;
+ Coordinate startPoint, endPoint, testPoint;
+
+ startIx = 0;
+ start = points.listIterator();
+ startPoint = start.next();
+ while ((start.hasNext()) && (startPoint != points.get(points.size() - 1))) {
+ entry = points.listIterator(points.size());
+ endPoint = entry.previous();
+ for (; endPoint != startPoint; endPoint = entry.previous()) {
+ entry2 = points.listIterator(start.nextIndex());
+ testPoint = entry2.next();
+ for (; testPoint != endPoint; testPoint = entry2.next()) {
+ if (pointDistanceFromLine(startPoint, endPoint, testPoint) > 0.001) {
+ break;
+ }
+ }
+ if ((testPoint == endPoint) && (endPoint != startPoint)) {
+ // Entire segment was within distance, it's a strait line.
+ // Remove all but the first and last point
+ entry2 = points.listIterator(start.nextIndex());
+ int nextIx = entry2.nextIndex();
+ Coordinate check = entry2.next();
+ while ((entry2.nextIndex() != points.size()) && (check != endPoint)) {
+ entry2.remove();
+ nextIx = entry2.nextIndex();
+ check = entry2.next();
+ }
+ startIx = nextIx;
+ start = points.listIterator(startIx);
+ startPoint = start.next();
+ break;
+ }
+ }
+ if (endPoint == startPoint) {
+ startIx = start.nextIndex();
+ if (start.hasNext())
+ startPoint = start.next();
+ }
+ }
+ }
+
+ private double pointDistanceFromLine(Coordinate startPoint, Coordinate endPoint, Coordinate testPoint) {
+ Coordinate pt = closestPointOnSegment(startPoint, endPoint, testPoint);
+
+ return testPoint.sub(pt).length();
+ }
+
+ private Coordinate closestPointOnSegment(Coordinate a, Coordinate b, Coordinate p) {
+ Coordinate D = b.sub(a);
+ double numer = p.sub(a).dot(D);
+ if (numer <= 0.0f)
+ return a;
+ double denom = D.dot(D);
+ if (numer >= denom)
+ return b;
+ return a.add(D.multiply(numer / denom));
+ }
+}
package net.sf.openrocket.gui.util;
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileFilter;
+
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
-import javax.swing.*;
-import javax.swing.filechooser.FileFilter;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-
/**
* Helper methods related to user-initiated file manipulation.
* <p>
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
// TODO: HIGH: Rename translation keys
/** File filter for any rocket designs (*.ork, *.rkt) */
public static final FileFilter CSV_FILE_FILTER =
new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv");
+ /** File filter for BMP files (*.bmp) */
+ public static final FileFilter BMP_FILE_FILTER =
+ new SimpleFileFilter(trans.get("CustomFinImport.filter"), ".bmp");
+
+
+
+
-
-
-
private FileHelper() {
// Prevent instantiation
}
+
+ // public FileFilter getImageFileFilter() {
+ // String[] extensions = ImageIO.getReaderFileSuffixes();
+ //
+ // }
+
+
/**
* Ensure that the provided file has a file extension. If the file does not have
* any extension, append the provided extension to it.
/**
* Ensure that the provided file has the given file extension. This differs from ensureExtension in that this
- * method guarantees that the file will have the extension, whereas ensureExtension only treats the extension
- * as a default.
+ * method guarantees that the file will have the extension, whereas ensureExtension only treats the extension
+ * as a default.
*
* @param original the original file
* @param extension the extension to guarantee (without preceding dot)
* @return the resulting file
*/
public static File forceExtension(File original, String extension) {
-
+
if (!original.getName().toLowerCase().endsWith(extension.toLowerCase())) {
log.debug(1, "File name does not contain extension, adding '" + extension + "'");
String name = original.getAbsolutePath();
- if (extension.startsWith(".")) {
- name = name + extension;
- }
- else {
- name = name + "." + extension;
- }
- return new File(name);
+ if (extension.startsWith(".")) {
+ name = name + extension;
+ }
+ else {
+ name = name + "." + extension;
+ }
+ return new File(name);
}
-
+
return original;
}
-
-
+
+
/**
* Confirm that it is allowed to write to a file. If the file exists,
* a confirmation dialog will be presented to the user to ensure overwriting is ok.
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.io.IOException;
+
+import net.sf.openrocket.startup.Application;
+
+public class LocalizedIOException extends IOException {
+
+ private static final Translator trans = Application.getTranslator();
+
+ private final String key;
+
+
+ public LocalizedIOException(String key) {
+ super(key);
+ this.key = key;
+ }
+
+ public LocalizedIOException(String key, Throwable cause) {
+ super(key, cause);
+ this.key = key;
+ }
+
+ @Override
+ public String getLocalizedMessage() {
+ return trans.get(key);
+ }
+}
this.length = points.get(points.size()-1).x - points.get(0).x;
}
*/
-
-
+
+
/**
* Convert an existing fin set into a freeform fin set. The specified
* fin set is taken out of the rocket tree (if any) and the new component
position = -1;
}
-
+
// Create the freeform fin set
Coordinate[] finpoints = finset.getFinPoints();
try {
}
-
+
/**
* 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.
}
public void setPoints(Coordinate[] points) throws IllegalFinPointException {
- ArrayList<Coordinate> list = new ArrayList<Coordinate>(points.length);
- for (Coordinate p : points) {
- list.add(p);
- }
+ setPoints(Arrays.asList(points));
+ }
+
+ public void setPoints(List<Coordinate> points) throws IllegalFinPointException {
+ ArrayList<Coordinate> list = new ArrayList<Coordinate>(points);
validate(list);
this.points = list;
- this.length = points[points.length - 1].x;
+ this.length = points.get(points.size() - 1).x;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
}
-
-
+
+
// Check for intersecting
double px0, py0, px1, py1;
px0 = 0;
}
-
+
private boolean intersects(double ax0, double ay0, double ax1, double ay1,
double bx0, double by0, double bx1, double by1) {
for (int i = 0; i < n - 1; i++) {
for (int j = i + 2; j < n - 1; j++) {
if (intersects(pts.get(i).x, pts.get(i).y, pts.get(i + 1).x, pts.get(i + 1).y,
- pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
+ pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
throw new IllegalFinPointException("segments intersect");
}
}