From: plaa Date: Wed, 14 Mar 2012 07:03:32 +0000 (+0000) Subject: Importing of image to freeform fin set X-Git-Tag: upstream/12.03~1^2~13 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=d50a8cec8f853189bbae90c25179b1971730fdac;p=debian%2Fopenrocket Importing of image to freeform fin set git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@458 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/core/ChangeLog b/core/ChangeLog index 3c29aed6..03c710d2 100644 --- a/core/ChangeLog +++ b/core/ChangeLog @@ -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 diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 102fd94b..19b13442 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -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 = Click to select    Shift+click to select other    Double-click to edit    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. + diff --git a/core/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/core/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 56609bcf..81f118cf 100644 --- a/core/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/core/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -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(component, "RelativePosition", - new RocketComponent.Position[] { - RocketComponent.Position.TOP, - RocketComponent.Position.MIDDLE, - RocketComponent.Position.BOTTOM, - RocketComponent.Position.ABSOLUTE - })); + combo = new JComboBox(new EnumModel(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(component, "CrossSection")); + combo = new JComboBox(new EnumModel(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 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; diff --git a/core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java index 34f3d646..3f5db045 100644 --- a/core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java +++ b/core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java @@ -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 null 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 options = new ArrayList(); - 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. */ diff --git a/core/src/net/sf/openrocket/gui/main/BasicFrame.java b/core/src/net/sf/openrocket/gui/main/BasicFrame.java index c522f2a2..c05c1ba3 100644 --- a/core/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/core/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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. diff --git a/core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java index 121dc0e0..a45dd51a 100644 --- a/core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java +++ b/core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java @@ -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, diff --git a/core/src/net/sf/openrocket/gui/util/CustomFinImporter.java b/core/src/net/sf/openrocket/gui/util/CustomFinImporter.java new file mode 100644 index 00000000..09eac8fb --- /dev/null +++ b/core/src/net/sf/openrocket/gui/util/CustomFinImporter.java @@ -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 getPoints(File file) throws IOException { + ArrayList points = new ArrayList(); + + 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 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 points) { + int startIx; + ListIterator 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)); + } +} diff --git a/core/src/net/sf/openrocket/gui/util/FileHelper.java b/core/src/net/sf/openrocket/gui/util/FileHelper.java index 2f7623d7..2edbb362 100644 --- a/core/src/net/sf/openrocket/gui/util/FileHelper.java +++ b/core/src/net/sf/openrocket/gui/util/FileHelper.java @@ -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. *

@@ -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. diff --git a/core/src/net/sf/openrocket/l10n/LocalizedIOException.java b/core/src/net/sf/openrocket/l10n/LocalizedIOException.java new file mode 100644 index 00000000..be99bdfb --- /dev/null +++ b/core/src/net/sf/openrocket/l10n/LocalizedIOException.java @@ -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); + } +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index ffa44be3..b0bb0ad7 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -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 index-1 and index. * 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 list = new ArrayList(points.length); - for (Coordinate p : points) { - list.add(p); - } + setPoints(Arrays.asList(points)); + } + + public void setPoints(List points) throws IllegalFinPointException { + ArrayList list = new ArrayList(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"); } }