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
* [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.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 &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.clickDrag = Click+drag: Add and move points
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
!InnerTubeConfig
@ -1568,3 +1573,13 @@ GuidedTourSelectionDialog.lbl.length = Number of slides:
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.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;
@ -14,6 +21,7 @@ import javax.swing.JSpinner;
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;
@ -25,9 +33,13 @@ import net.sf.openrocket.gui.adaptors.IntegerModel;
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;
@ -56,18 +68,16 @@ public class FreeformFinSetConfig extends FinSetConfig {
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;
@ -78,8 +88,8 @@ public class FreeformFinSetConfig extends FinSetConfig {
JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel", "[][65lp::][30lp::]", ""));
//// Number of fins:
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Numberoffins")));
@ -89,7 +99,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "growx, wrap");
//// Base rotation
panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Finrotation")));
@ -102,39 +112,31 @@ public class FreeformFinSetConfig extends FinSetConfig {
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");
@ -145,32 +147,28 @@ public class FreeformFinSetConfig extends FinSetConfig {
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")));
@ -183,23 +181,23 @@ public class FreeformFinSetConfig extends FinSetConfig {
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();
@ -211,35 +209,86 @@ public class FreeformFinSetConfig extends FinSetConfig {
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();
@ -253,13 +302,10 @@ public class FreeformFinSetConfig extends FinSetConfig {
}
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;
@ -271,8 +317,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
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;
}
@ -303,9 +348,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@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;
}
@ -314,8 +357,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
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);
}
}
@ -329,8 +371,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@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;
}
@ -375,13 +416,13 @@ public class FreeformFinSetConfig extends FinSetConfig {
return figure.convertPoint(x, y);
}
}
private enum Columns {
// NUMBER {
// @Override
@ -405,8 +446,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@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 {
@ -417,8 +457,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@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);
}
};
@ -468,10 +507,8 @@ public class FreeformFinSetConfig extends FinSetConfig {
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;

View File

@ -67,7 +67,7 @@ public class ScaleDialog extends JDialog {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
/*
* Scaler implementations
*
@ -179,9 +179,9 @@ public class ScaleDialog extends JDialog {
}
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;
@ -190,18 +190,19 @@ public class ScaleDialog extends JDialog {
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;
@ -213,14 +214,32 @@ public class ScaleDialog extends JDialog {
* @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);
}
@ -228,7 +247,7 @@ public class ScaleDialog extends JDialog {
options.add(SCALE_SELECTION);
}
/*
* Select initial size for "from" field.
*
@ -262,7 +281,7 @@ public class ScaleDialog extends JDialog {
fromField.setValue(initialSize);
toField.setValue(initialSize);
// Add actions to the values
multiplier.addChangeListener(new ChangeListener() {
@Override
@ -295,13 +314,13 @@ public class ScaleDialog extends JDialog {
}
});
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"));
@ -313,14 +332,14 @@ public class ScaleDialog extends JDialog {
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);
@ -333,7 +352,7 @@ public class ScaleDialog extends JDialog {
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"));
@ -362,7 +381,7 @@ public class ScaleDialog extends JDialog {
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"));
@ -377,7 +396,7 @@ public class ScaleDialog extends JDialog {
scaleMassValues.setEnabled(overridden);
panel.add(scaleMassValues, "span, wrap para*3");
// Buttons
JButton scale = new JButton(trans.get("button.scale"));
@ -399,13 +418,13 @@ public class ScaleDialog extends JDialog {
});
panel.add(cancel, "right, gap para");
GUIUtil.setDisposableDialogOptions(this, scale);
}
private void doScale() {
double mul = multiplier.getValue();
if (!(SCALE_MIN <= mul && mul <= SCALE_MAX)) {
@ -502,7 +521,7 @@ public class ScaleDialog extends JDialog {
}
/**
* Interface for scaling a specific component/value.
*/

View File

@ -152,6 +152,7 @@ public class BasicFrame extends JFrame {
/**
* Sole constructor. Creates a new frame based on the supplied document
* 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.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@ -52,10 +51,9 @@ public class ScaleScrollPane extends JScrollPane
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;
@ -92,7 +90,7 @@ public class ScaleScrollPane extends JScrollPane
this.figure = (ScaleFigure) component;
this.allowFit = allowFit;
rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
rulerUnit.addChangeListener(new ChangeListener() {
@Override
@ -114,8 +112,7 @@ public class ScaleScrollPane extends JScrollPane
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
viewport = this.getViewport();
viewport.addMouseListener(this);
viewport.addMouseMotionListener(this);
@ -183,7 +180,7 @@ public class ScaleScrollPane extends JScrollPane
}
public double getScaling() {
return figure.getScaling();
}
@ -209,7 +206,7 @@ public class ScaleScrollPane extends JScrollPane
//////////////// Mouse handlers ////////////////
private int dragStartX = 0;
private int dragStartY = 0;
private Rectangle dragRectangle = null;
@ -257,10 +254,10 @@ public class ScaleScrollPane extends JScrollPane
}
//////////////// The view port rulers ////////////////
private class Ruler extends JComponent {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
@ -326,7 +323,7 @@ public class ScaleScrollPane extends JScrollPane
g2.setColor(getBackground());
g2.fillRect(area.x, area.y, area.width, area.height + 100);
int startpx, endpx;
if (orientation == HORIZONTAL) {
startpx = area.x;
@ -345,7 +342,7 @@ public class ScaleScrollPane extends JScrollPane
Tick[] ticks = unit.getTicks(start, end, minor, major);
// Set color & hints
g2.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,

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;
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>
@ -22,7 +23,7 @@ public final class FileHelper {
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) */
@ -46,14 +47,25 @@ public final class FileHelper {
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.
@ -76,31 +88,31 @@ public final class FileHelper {
/**
* 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.

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

@ -44,8 +44,8 @@ public class FreeformFinSet extends FinSet {
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
@ -77,7 +77,7 @@ public class FreeformFinSet extends FinSet {
position = -1;
}
// Create the freeform fin set
Coordinate[] finpoints = finset.getFinPoints();
try {
@ -118,7 +118,7 @@ public class FreeformFinSet extends FinSet {
}
/**
* 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.
@ -166,14 +166,15 @@ public class FreeformFinSet extends FinSet {
}
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);
}
@ -230,8 +231,8 @@ public class FreeformFinSet extends FinSet {
}
// Check for intersecting
double px0, py0, px1, py1;
px0 = 0;
@ -275,7 +276,7 @@ public class FreeformFinSet extends FinSet {
}
private boolean intersects(double ax0, double ay0, double ax1, double ay1,
double bx0, double by0, double bx1, double by1) {
@ -326,7 +327,7 @@ public class FreeformFinSet extends FinSet {
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");
}
}