Importing of image to freeform fin set
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Wed, 14 Mar 2012 07:03:32 +0000 (07:03 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Wed, 14 Mar 2012 07:03:32 +0000 (07:03 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@458 180e2498-e6e9-4542-8430-84ac67f01cd8

core/ChangeLog
core/resources/l10n/messages.properties
core/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java
core/src/net/sf/openrocket/gui/main/BasicFrame.java
core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
core/src/net/sf/openrocket/gui/util/CustomFinImporter.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/util/FileHelper.java
core/src/net/sf/openrocket/l10n/LocalizedIOException.java [new file with mode: 0644]
core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java

index 3c29aed69d2b28a8e19559cb883f29fb03c3df4b..03c710d28bebb81e32fa0fc0f0a73d52ed678e31 100644 (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
index 102fd94b54cd18644cb8b1b7115a9f69e4ece189..19b13442e3b3e39294390c7e34d9fbbcbe5a71a7 100644 (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.
+
index 56609bcf555a0f678b7ee192d2d64d46cf373ded..81f118cf4ad7c969c5182e7ad3f3811391b82d0d 100644 (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;
index 34f3d646d9a615d057aa1ef63479d2c39514f7a2..3f5db045f2e6b1b71bdf0ee42d4704713f22f4c9 100644 (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.
         */
index c522f2a22536dc318f2806eb64bb553865b8e44b..c05c1ba34dd82b9a39ded5737a5d2a9336445bb3 100644 (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.
index 121dc0e0bbd4e603c8652dce7977f921edb0e3b8..a45dd51a8e71b48f0d6c08b91bb3c63f0a0edd31 100644 (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,
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 (file)
index 0000000..09eac8f
--- /dev/null
@@ -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));
+       }
+}
index 2f7623d7b42b4164d83b99ebace51d83f6a9c87d..2edbb362ee8896b4e372042e8f1372b508fd3c44 100644 (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.
diff --git a/core/src/net/sf/openrocket/l10n/LocalizedIOException.java b/core/src/net/sf/openrocket/l10n/LocalizedIOException.java
new file mode 100644 (file)
index 0000000..be99bdf
--- /dev/null
@@ -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);
+       }
+}
index ffa44be37b0bc04e1130afdaec234e064f007aa0..b0bb0ad716e639cefd2fa048605e311a55d32361 100644 (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");
                                }
                        }