Importing of image to freeform fin set

This commit is contained in:
Sampo Niskanen 2012-03-14 07:03:32 +00:00
parent 34b6151596
commit d320dc7086
10 changed files with 541 additions and 157 deletions

View File

@ -1,3 +1,7 @@
2012-03-14 Jason Blood
* Importing images to freeform fin sets
2012-03-13 Sampo Niskanne 2012-03-13 Sampo Niskanne
* [BUG] Threads piled up when running simulations * [BUG] Threads piled up when running simulations

View File

@ -49,6 +49,10 @@ RocketPanel.FigTypeAct.Sideview = Side view
RocketPanel.FigTypeAct.ttip.Sideview = Side view RocketPanel.FigTypeAct.ttip.Sideview = Side view
RocketPanel.FigTypeAct.Backview = Back view RocketPanel.FigTypeAct.Backview = Back view
RocketPanel.FigTypeAct.ttip.Backview = Rear 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.Motorcfg = Motor configuration:
RocketPanel.lbl.infoMessage = <html>Click to select &nbsp;&nbsp; Shift+click to select other &nbsp;&nbsp; Double-click to edit &nbsp;&nbsp; Click+drag to move RocketPanel.lbl.infoMessage = <html>Click to select &nbsp;&nbsp; Shift+click to select other &nbsp;&nbsp; Double-click to edit &nbsp;&nbsp; Click+drag to move
@ -713,6 +717,7 @@ FreeformFinSetConfig.lbl.doubleClick1 = Double-click
FreeformFinSetConfig.lbl.doubleClick2 = to edit FreeformFinSetConfig.lbl.doubleClick2 = to edit
FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
!InnerTubeConfig !InnerTubeConfig
@ -1568,3 +1573,13 @@ GuidedTourSelectionDialog.lbl.length = Number of slides:
GuidedTourSelectionDialog.btn.start = Start tour! 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.

View File

@ -2,11 +2,18 @@ package net.sf.openrocket.gui.configdialog;
import java.awt.Point; import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JSeparator; import javax.swing.JSeparator;
@ -14,6 +21,7 @@ import javax.swing.JSpinner;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
@ -25,9 +33,13 @@ import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector; 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.FinPointFigure;
import net.sf.openrocket.gui.scalefigure.ScaleScrollPane; import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
import net.sf.openrocket.gui.scalefigure.ScaleSelector; 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.l10n.Translator;
import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material; import net.sf.openrocket.material.Material;
@ -56,11 +68,9 @@ public class FreeformFinSetConfig extends FinSetConfig {
this.finset = (FreeformFinSet) component; this.finset = (FreeformFinSet) component;
//// General and General properties //// General and General properties
tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(), tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.General"), null, generalPane(), trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
trans.get("FreeformFinSetCfg.tab.ttip.General"), 0);
//// Shape and Fin shape //// Shape and Fin shape
tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(), tabbedPane.insertTab(trans.get("FreeformFinSetCfg.tab.Shape"), null, shapePane(), trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
trans.get("FreeformFinSetCfg.tab.ttip.Finshape"), 1);
tabbedPane.setSelectedIndex(0); tabbedPane.setSelectedIndex(0);
addFinSetButtons(); addFinSetButtons();
@ -110,16 +120,14 @@ public class FreeformFinSetConfig extends FinSetConfig {
label.setToolTipText(trans.get("FreeformFinSetCfg.lbl.ttip.Fincant")); label.setToolTipText(trans.get("FreeformFinSetCfg.lbl.ttip.Fincant"));
panel.add(label); panel.add(label);
m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, m = new DoubleModel(component, "CantAngle", UnitGroup.UNITS_ANGLE, -FinSet.MAX_CANT, FinSet.MAX_CANT);
-FinSet.MAX_CANT, FinSet.MAX_CANT);
spin = new JSpinner(m.getSpinnerModel()); spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin)); spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx"); panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx"); panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), panel.add(new BasicSlider(m.getSliderModel(-FinSet.MAX_CANT, FinSet.MAX_CANT)), "w 100lp, wrap 40lp");
"w 100lp, wrap 40lp");
@ -127,14 +135,8 @@ public class FreeformFinSetConfig extends FinSetConfig {
//// Position relative to: //// Position relative to:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto"))); panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto")));
combo = new JComboBox( combo = new JComboBox(new EnumModel<RocketComponent.Position>(component, "RelativePosition", new RocketComponent.Position[] { RocketComponent.Position.TOP, RocketComponent.Position.MIDDLE,
new EnumModel<RocketComponent.Position>(component, "RelativePosition", RocketComponent.Position.BOTTOM, RocketComponent.Position.ABSOLUTE }));
new RocketComponent.Position[] {
RocketComponent.Position.TOP,
RocketComponent.Position.MIDDLE,
RocketComponent.Position.BOTTOM,
RocketComponent.Position.ABSOLUTE
}));
panel.add(combo, "spanx 3, growx, wrap"); panel.add(combo, "spanx 3, growx, wrap");
//// plus //// plus
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right"); panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right");
@ -145,10 +147,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
panel.add(spin, "growx"); panel.add(spin, "growx");
panel.add(new UnitSelector(m), "growx"); panel.add(new UnitSelector(m), "growx");
panel.add(new BasicSlider(m.getSliderModel( panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), new DoubleModel(component.getParent(), "Length"))), "w 100lp, wrap");
new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE),
new DoubleModel(component.getParent(), "Length"))),
"w 100lp, wrap");
@ -166,8 +165,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
//// Cross section //// Cross section
//// Fin cross section: //// Fin cross section:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split"); panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split");
combo = new JComboBox( combo = new JComboBox(new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
new EnumModel<FinSet.CrossSection>(component, "CrossSection"));
panel.add(combo, "growx, wrap unrel"); panel.add(combo, "growx, wrap unrel");
@ -211,27 +209,46 @@ public class FreeformFinSetConfig extends FinSetConfig {
table = new JTable(tableModel); table = new JTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
for (int i = 0; i < Columns.values().length; i++) { for (int i = 0; i < Columns.values().length; i++) {
table.getColumnModel().getColumn(i). table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth());
setPreferredWidth(Columns.values()[i].getWidth());
} }
JScrollPane tablePane = new JScrollPane(table); 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("Coordinates:"), "aligny bottom, alignx 50%");
// panel.add(new JLabel(" View:"), "wrap, aligny bottom"); // panel.add(new JLabel(" View:"), "wrap, aligny bottom");
panel.add(tablePane, "growy, width 100lp:100lp:, height 100lp:250lp:"); 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(scaleButton, "spany 2, alignx 50%, aligny 50%");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag") + " " + panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%");
trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spany 2, right, wrap");
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 StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%"); // 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; return panel;
} }
@ -240,6 +257,38 @@ public class FreeformFinSetConfig extends FinSetConfig {
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 @Override
public void updateFields() { public void updateFields() {
super.updateFields(); super.updateFields();
@ -256,10 +305,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
private class FinPointScrollPane extends ScaleScrollPane { private class FinPointScrollPane extends ScaleScrollPane {
private static final int ANY_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);
(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; private int dragIndex = -1;
@ -271,8 +317,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
public void mousePressed(MouseEvent event) { public void mousePressed(MouseEvent event) {
int mods = event.getModifiersEx(); int mods = event.getModifiersEx();
if (event.getButton() != MouseEvent.BUTTON1 || if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) {
(mods & ANY_MASK) != 0) {
super.mousePressed(event); super.mousePressed(event);
return; return;
} }
@ -303,9 +348,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public void mouseDragged(MouseEvent event) { public void mouseDragged(MouseEvent event) {
int mods = event.getModifiersEx(); int mods = event.getModifiersEx();
if (dragIndex < 0 || if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
(mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) !=
MouseEvent.BUTTON1_DOWN_MASK) {
super.mouseDragged(event); super.mouseDragged(event);
return; return;
} }
@ -314,8 +357,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
try { try {
finset.setPoint(dragIndex, point.x, point.y); finset.setPoint(dragIndex, point.x, point.y);
} catch (IllegalFinPointException ignore) { } catch (IllegalFinPointException ignore) {
log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
" x=" + point.x + " y=" + point.y);
} }
} }
@ -329,8 +371,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public void mouseClicked(MouseEvent event) { public void mouseClicked(MouseEvent event) {
int mods = event.getModifiersEx(); int mods = event.getModifiersEx();
if (event.getButton() != MouseEvent.BUTTON1 || if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
(mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) {
super.mouseClicked(event); super.mouseClicked(event);
return; return;
} }
@ -405,8 +446,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public String getValue(FreeformFinSet finset, int row) { public String getValue(FreeformFinSet finset, int row) {
return UnitGroup.UNITS_LENGTH.getDefaultUnit() return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].x);
.toString(finset.getFinPoints()[row].x);
} }
}, },
Y { Y {
@ -417,8 +457,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public String getValue(FreeformFinSet finset, int row) { public String getValue(FreeformFinSet finset, int row) {
return UnitGroup.UNITS_LENGTH.getDefaultUnit() return UnitGroup.UNITS_LENGTH.getDefaultUnit().toString(finset.getFinPoints()[row].y);
.toString(finset.getFinPoints()[row].y);
} }
}; };
@ -468,10 +507,8 @@ public class FreeformFinSetConfig extends FinSetConfig {
if (!(o instanceof String)) if (!(o instanceof String))
return; return;
if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || columnIndex < 0 || columnIndex >= Columns.values().length) {
columnIndex < 0 || columnIndex >= Columns.values().length) { throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex + " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex +
" column=" + columnIndex + " fin point count=" + finset.getFinPoints().length);
} }
String str = (String) o; String str = (String) o;

View File

@ -199,9 +199,10 @@ public class ScaleDialog extends JDialog {
private final OpenRocketDocument document; private final OpenRocketDocument document;
private final RocketComponent selection; private final RocketComponent selection;
private final boolean onlySelection;
private final JComboBox selectionOption; private JComboBox selectionOption;
private final JCheckBox scaleMassValues; private JCheckBox scaleMassValues;
private boolean changing = false; private boolean changing = false;
@ -213,14 +214,32 @@ public class ScaleDialog extends JDialog {
* @param parent the parent window. * @param parent the parent window.
*/ */
public ScaleDialog(OpenRocketDocument document, RocketComponent selection, Window parent) { 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); super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
this.document = document; this.document = document;
this.selection = selection; this.selection = selection;
this.onlySelection = onlySelection;
init();
}
private void init() {
// Generate options for scaling // Generate options for scaling
List<String> options = new ArrayList<String>(); List<String> options = new ArrayList<String>();
options.add(SCALE_ROCKET); if (!onlySelection)
options.add(SCALE_ROCKET);
if (selection != null && selection.getChildCount() > 0) { if (selection != null && selection.getChildCount() > 0) {
options.add(SCALE_SUBSELECTION); options.add(SCALE_SUBSELECTION);
} }

View File

@ -152,6 +152,7 @@ public class BasicFrame extends JFrame {
/** /**
* Sole constructor. Creates a new frame based on the supplied document * Sole constructor. Creates a new frame based on the supplied document
* and adds it to the current frames list. * and adds it to the current frames list.

View File

@ -19,7 +19,6 @@ import javax.swing.BorderFactory;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
@ -55,7 +54,6 @@ public class ScaleScrollPane extends JScrollPane
private JComponent component; private JComponent component;
private ScaleFigure figure; private ScaleFigure figure;
private JViewport viewport;
private DoubleModel rulerUnit; private DoubleModel rulerUnit;
private Ruler horizontalRuler; private Ruler horizontalRuler;
@ -115,7 +113,6 @@ public class ScaleScrollPane extends JScrollPane
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
viewport = this.getViewport();
viewport.addMouseListener(this); viewport.addMouseListener(this);
viewport.addMouseMotionListener(this); viewport.addMouseMotionListener(this);

View File

@ -0,0 +1,270 @@
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));
}
}

View File

@ -1,16 +1,17 @@
package net.sf.openrocket.gui.util; 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.L10N;
import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application; 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. * Helper methods related to user-initiated file manipulation.
* <p> * <p>
@ -46,6 +47,10 @@ public final class FileHelper {
public static final FileFilter CSV_FILE_FILTER = public static final FileFilter CSV_FILE_FILTER =
new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv"); 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");
@ -54,6 +59,13 @@ public final class FileHelper {
// Prevent instantiation // Prevent instantiation
} }
// public FileFilter getImageFileFilter() {
// String[] extensions = ImageIO.getReaderFileSuffixes();
//
// }
/** /**
* Ensure that the provided file has a file extension. If the file does not have * Ensure that the provided file has a file extension. If the file does not have
* any extension, append the provided extension to it. * any extension, append the provided extension to it.
@ -76,8 +88,8 @@ public final class FileHelper {
/** /**
* Ensure that the provided file has the given file extension. This differs from ensureExtension in that this * 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 * method guarantees that the file will have the extension, whereas ensureExtension only treats the extension
* as a default. * as a default.
* *
* @param original the original file * @param original the original file
* @param extension the extension to guarantee (without preceding dot) * @param extension the extension to guarantee (without preceding dot)
@ -88,13 +100,13 @@ public final class FileHelper {
if (!original.getName().toLowerCase().endsWith(extension.toLowerCase())) { if (!original.getName().toLowerCase().endsWith(extension.toLowerCase())) {
log.debug(1, "File name does not contain extension, adding '" + extension + "'"); log.debug(1, "File name does not contain extension, adding '" + extension + "'");
String name = original.getAbsolutePath(); String name = original.getAbsolutePath();
if (extension.startsWith(".")) { if (extension.startsWith(".")) {
name = name + extension; name = name + extension;
} }
else { else {
name = name + "." + extension; name = name + "." + extension;
} }
return new File(name); return new File(name);
} }
return original; return original;

View File

@ -0,0 +1,28 @@
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);
}
}

View File

@ -166,14 +166,15 @@ public class FreeformFinSet extends FinSet {
} }
public void setPoints(Coordinate[] points) throws IllegalFinPointException { public void setPoints(Coordinate[] points) throws IllegalFinPointException {
ArrayList<Coordinate> list = new ArrayList<Coordinate>(points.length); setPoints(Arrays.asList(points));
for (Coordinate p : points) { }
list.add(p);
} public void setPoints(List<Coordinate> points) throws IllegalFinPointException {
ArrayList<Coordinate> list = new ArrayList<Coordinate>(points);
validate(list); validate(list);
this.points = list; this.points = list;
this.length = points[points.length - 1].x; this.length = points.get(points.size() - 1).x;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
} }
@ -326,7 +327,7 @@ public class FreeformFinSet extends FinSet {
for (int i = 0; i < n - 1; i++) { for (int i = 0; i < n - 1; i++) {
for (int j = i + 2; j < n - 1; j++) { 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, 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"); throw new IllegalFinPointException("segments intersect");
} }
} }