Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / net / sf / openrocket / gui / scalefigure / RocketPanel.java
index cf458724344b770483d96a12decd627fff37285e..2cef70809bbab6a969e2a4aee1e17f269514fcfa 100644 (file)
@@ -1,6 +1,7 @@
 package net.sf.openrocket.gui.scalefigure;
 
 
+import java.awt.BorderLayout;
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Point;
@@ -18,6 +19,7 @@ import java.util.concurrent.ThreadFactory;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
+import javax.swing.ButtonGroup;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
@@ -43,6 +45,7 @@ import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.StageSelector;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
+import net.sf.openrocket.gui.figure3d.RocketFigure3d;
 import net.sf.openrocket.gui.figureelements.CGCaret;
 import net.sf.openrocket.gui.figureelements.CPCaret;
 import net.sf.openrocket.gui.figureelements.Caret;
@@ -59,6 +62,8 @@ import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
 import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpressionSimulationListener;
 import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
 import net.sf.openrocket.simulation.listeners.system.InterruptListener;
@@ -74,45 +79,57 @@ import net.sf.openrocket.util.StateChangeListener;
  * A JPanel that contains a RocketFigure and buttons to manipulate the figure. 
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @author Bill Kuker <bkuker@billkuker.com>
  */
 public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource {
-       
+       private static final long serialVersionUID = 1L;
+
        private static final Translator trans = Application.getTranslator();
+
+       private boolean is3d;
        private final RocketFigure figure;
+       private final RocketFigure3d figure3d;
+
+
        private final ScaleScrollPane scrollPane;
-       
+
+       private final JPanel figureHolder;
+
        private JLabel infoMessage;
-       
+
        private TreeSelectionModel selectionModel = null;
-       
+
+       private BasicSlider rotationSlider;
+       ScaleSelector scaleSelector;
+
 
        /* Calculation of CP and CG */
        private AerodynamicCalculator aerodynamicCalculator;
        private MassCalculator massCalculator;
-       
+
 
        private final OpenRocketDocument document;
        private final Configuration configuration;
-       
+
        private Caret extraCP = null;
        private Caret extraCG = null;
        private RocketInfo extraText = null;
-       
+
 
        private double cpAOA = Double.NaN;
        private double cpTheta = Double.NaN;
        private double cpMach = Double.NaN;
        private double cpRoll = Double.NaN;
-       
+
        // The functional ID of the rocket that was simulated
        private int flightDataFunctionalID = -1;
        private String flightDataMotorID = null;
-       
+
 
        private SimulationWorker backgroundSimulationWorker = null;
-       
+
        private List<EventListener> listeners = new ArrayList<EventListener>();
-       
+
 
        /**
         * The executor service used for running the background simulations.
@@ -123,32 +140,37 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        static {
                backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(),
                                new ThreadFactory() {
-                                       private ThreadFactory factory = Executors.defaultThreadFactory();
-                                       
-                                       @Override
-                                       public Thread newThread(Runnable r) {
-                                               Thread t = factory.newThread(r);
-                                               t.setDaemon(true);
-                                               t.setPriority(Thread.MIN_PRIORITY);
-                                               return t;
-                                       }
-                               });
+                       private ThreadFactory factory = Executors.defaultThreadFactory();
+
+                       @Override
+                       public Thread newThread(Runnable r) {
+                               Thread t = factory.newThread(r);
+                               t.setDaemon(true);
+                               t.setPriority(Thread.MIN_PRIORITY);
+                               return t;
+                       }
+               });
        }
-       
-       
+
+
        public RocketPanel(OpenRocketDocument document) {
-               
+
                this.document = document;
                configuration = document.getDefaultConfiguration();
-               
+
                // TODO: FUTURE: calculator selection
                aerodynamicCalculator = new BarrowmanCalculator();
                massCalculator = new BasicMassCalculator();
-               
+
                // Create figure and custom scroll pane
                figure = new RocketFigure(configuration);
-               
+               figure3d = new RocketFigure3d(configuration);
+
+               figureHolder = new JPanel(new BorderLayout());
+
                scrollPane = new ScaleScrollPane(figure) {
+                       private static final long serialVersionUID = 1L;
+
                        @Override
                        public void mouseClicked(MouseEvent event) {
                                handleMouseClick(event);
@@ -156,31 +178,77 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                };
                scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
                scrollPane.setFitting(true);
-               
+
                createPanel();
-               
+
+               is3d = true;
+               go2D();
+
                configuration.addChangeListener(new StateChangeListener() {
                        @Override
                        public void stateChanged(EventObject e) {
                                // System.out.println("Configuration changed, calling updateFigure");
                                updateExtras();
-                               figure.updateFigure();
+                               updateFigures();
+                       }
+               });
+
+               figure3d.addComponentSelectionListener(new RocketFigure3d.ComponentSelectionListener() {
+                       @Override
+                       public void componentClicked(RocketComponent clicked[], MouseEvent event) {
+                               handleComponentClick(clicked, event);
                        }
                });
        }
-       
-       
+
+       private void updateFigures() {
+               if (!is3d)
+                       figure.updateFigure();
+               else
+                       figure3d.updateFigure();
+       }
+
+       private void go3D() {
+               if (is3d)
+                       return;
+               is3d = true;
+               figureHolder.remove(scrollPane);
+               figureHolder.add(figure3d, BorderLayout.CENTER);
+               rotationSlider.setEnabled(false);
+               scaleSelector.setEnabled(false);
+
+               revalidate();
+               figureHolder.revalidate();
+
+               figure3d.repaint();
+       }
+
+       private void go2D() {
+               if (!is3d)
+                       return;
+               is3d = false;
+               figureHolder.remove(figure3d);
+               figureHolder.add(scrollPane, BorderLayout.CENTER);
+               rotationSlider.setEnabled(true);
+               scaleSelector.setEnabled(true);
+               revalidate();
+               figureHolder.revalidate();
+               figure.repaint();
+       }
+
        /**
         * Creates the layout and components of the panel.
         */
        private void createPanel() {
                setLayout(new MigLayout("", "[shrink][grow]", "[shrink][shrink][grow][shrink]"));
-               
+
                setPreferredSize(new Dimension(800, 300));
-               
+
 
                //// Create toolbar
-               
+
+               ButtonGroup bg = new ButtonGroup();
+
                // Side/back buttons
                FigureTypeAction action = new FigureTypeAction(RocketFigure.TYPE_SIDE);
                //// Side view
@@ -188,27 +256,48 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                //// Side view
                action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Sideview"));
                JToggleButton toggle = new JToggleButton(action);
+               bg.add(toggle);
                add(toggle, "spanx, split");
-               
+
                action = new FigureTypeAction(RocketFigure.TYPE_BACK);
                //// Back view
                action.putValue(Action.NAME, trans.get("RocketPanel.FigTypeAct.Backview"));
                //// Back view
                action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Backview"));
                toggle = new JToggleButton(action);
+               bg.add(toggle);
                add(toggle, "gap rel");
-               
+
+               //// 3d Toggle
+               final JToggleButton toggle3d = new JToggleButton(new AbstractAction("3D") {
+                       private static final long serialVersionUID = 1L;
+                       {
+                               putValue(Action.NAME, "3D");//TODO
+                               putValue(Action.SHORT_DESCRIPTION, "3D"); //TODO
+                       }
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               if ( ((JToggleButton)e.getSource()).isSelected() ){
+                                       go3D();
+                               } else {
+                                       go2D();
+                               }
+                       }
+               });
+               bg.add(toggle3d);
+               toggle3d.setEnabled(RocketFigure3d.is3dEnabled());
+               add(toggle3d, "gap rel");
 
                // Zoom level selector
-               ScaleSelector scaleSelector = new ScaleSelector(scrollPane);
+               scaleSelector = new ScaleSelector(scrollPane);
                add(scaleSelector);
-               
+
 
 
                // Stage selector
                StageSelector stageSelector = new StageSelector(configuration);
                add(stageSelector, "");
-               
+
 
 
                // Motor configuration selector
@@ -217,55 +306,55 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                label.setHorizontalAlignment(JLabel.RIGHT);
                add(label, "growx, right");
                add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap");
-               
+
 
 
 
 
                // Create slider and scroll pane
-               
+
                DoubleModel theta = new DoubleModel(figure, "Rotation",
                                UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
                UnitSelector us = new UnitSelector(theta, true);
                us.setHorizontalAlignment(JLabel.CENTER);
                add(us, "alignx 50%, growx");
-               
+
                // Add the rocket figure
-               add(scrollPane, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
-               
+               add(figureHolder, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
+
 
                // Add rotation slider
                // Minimum size to fit "360deg"
                JLabel l = new JLabel("360" + Chars.DEGREE);
                Dimension d = l.getPreferredSize();
-               
-               add(new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
+
+               add(rotationSlider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
                                "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy");
-               
+
 
                //// <html>Click to select &nbsp;&nbsp; Shift+click to select other &nbsp;&nbsp; Double-click to edit &nbsp;&nbsp; Click+drag to move
                infoMessage = new JLabel(trans.get("RocketPanel.lbl.infoMessage"));
                infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9));
                add(infoMessage, "skip, span, gapleft 25, wrap");
-               
+
 
                addExtras();
        }
-       
-       
+
+
 
        public RocketFigure getFigure() {
                return figure;
        }
-       
+
        public AerodynamicCalculator getAerodynamicCalculator() {
                return aerodynamicCalculator;
        }
-       
+
        public Configuration getConfiguration() {
                return configuration;
        }
-       
+
        /**
         * Get the center of pressure figure element.
         * 
@@ -274,7 +363,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        public Caret getExtraCP() {
                return extraCP;
        }
-       
+
        /**
         * Get the center of gravity figure element.
         * 
@@ -283,7 +372,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        public Caret getExtraCG() {
                return extraCG;
        }
-       
+
        /**
         * Get the extra text figure element.
         * 
@@ -292,7 +381,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        public RocketInfo getExtraText() {
                return extraText;
        }
-       
+
        public void setSelectionModel(TreeSelectionModel m) {
                if (selectionModel != null) {
                        selectionModel.removeTreeSelectionListener(this);
@@ -301,8 +390,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                selectionModel.addTreeSelectionListener(this);
                valueChanged((TreeSelectionEvent) null); // updates FigureParameters
        }
-       
-       
+
+
 
        /**
         * Return the angle of attack used in CP calculation.  NaN signifies the default value
@@ -312,7 +401,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        public double getCPAOA() {
                return cpAOA;
        }
-       
+
        /**
         * Set the angle of attack to be used in CP calculation.  A value of NaN signifies that
         * the default AOA (zero) should be used.
@@ -324,14 +413,14 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        return;
                cpAOA = aoa;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
-       
+
        public double getCPTheta() {
                return cpTheta;
        }
-       
+
        public void setCPTheta(double theta) {
                if (MathUtil.equals(theta, cpTheta) ||
                                (Double.isNaN(theta) && Double.isNaN(cpTheta)))
@@ -340,50 +429,50 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                if (!Double.isNaN(theta))
                        figure.setRotation(theta);
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
-       
+
        public double getCPMach() {
                return cpMach;
        }
-       
+
        public void setCPMach(double mach) {
                if (MathUtil.equals(mach, cpMach) ||
                                (Double.isNaN(mach) && Double.isNaN(cpMach)))
                        return;
                cpMach = mach;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
-       
+
        public double getCPRoll() {
                return cpRoll;
        }
-       
+
        public void setCPRoll(double roll) {
                if (MathUtil.equals(roll, cpRoll) ||
                                (Double.isNaN(roll) && Double.isNaN(cpRoll)))
                        return;
                cpRoll = roll;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
-       
-       
+
+
 
        @Override
        public void addChangeListener(EventListener listener) {
                listeners.add(0, listener);
        }
-       
+
        @Override
        public void removeChangeListener(EventListener listener) {
                listeners.remove(listener);
        }
-       
+
        protected void fireChangeEvent() {
                EventObject e = new EventObject(this);
                for (EventListener l : listeners) {
@@ -392,8 +481,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        }
                }
        }
-       
-       
+
+
 
 
        /**
@@ -406,7 +495,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * the next component. Otherwise select the first component in the list. 
         */
        public static final int CYCLE_SELECTION_MODIFIER = InputEvent.SHIFT_DOWN_MASK;
-       
+
        private void handleMouseClick(MouseEvent event) {
                if (event.getButton() != MouseEvent.BUTTON1)
                        return;
@@ -414,13 +503,18 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                Point p1 = scrollPane.getViewport().getViewPosition();
                int x = p0.x + p1.x;
                int y = p0.y + p1.y;
-               
+
                RocketComponent[] clicked = figure.getComponentsByPoint(x, y);
-               
+
+               handleComponentClick(clicked, event);
+       }
+
+       private void handleComponentClick(RocketComponent[] clicked, MouseEvent event){
+
                // If no component is clicked, do nothing
                if (clicked.length == 0)
                        return;
-               
+
                // Check whether the currently selected component is in the clicked components.
                TreePath path = selectionModel.getSelectionPath();
                if (path != null) {
@@ -437,7 +531,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                                }
                        }
                }
-               
+
                // Currently selected component not clicked
                if (path == null) {
                        if (event.isShiftDown() && event.getClickCount() == 1 && clicked.length > 1) {
@@ -446,18 +540,18 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                                path = ComponentTreeModel.makeTreePath(clicked[0]);
                        }
                }
-               
+
                // Set selection and check for double-click
                selectionModel.setSelectionPath(path);
                if (event.getClickCount() == 2) {
                        RocketComponent component = (RocketComponent) path.getLastPathComponent();
-                       
+
                        ComponentConfigDialog.showDialog(SwingUtilities.getWindowAncestor(this),
                                        document, component);
                }
        }
-       
-       
+
+
 
 
        /**
@@ -465,15 +559,15 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * the CP and CG carets.
         */
        private WarningSet warnings = new WarningSet();
-       
+
        private void updateExtras() {
                Coordinate cp, cg;
                double cpx, cgx;
-               
+
                // TODO: MEDIUM: User-definable conditions
                FlightConditions conditions = new FlightConditions(configuration);
                warnings.clear();
-               
+
                if (!Double.isNaN(cpMach)) {
                        conditions.setMach(cpMach);
                        extraText.setMach(cpMach);
@@ -481,20 +575,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        conditions.setMach(Application.getPreferences().getDefaultMach());
                        extraText.setMach(Application.getPreferences().getDefaultMach());
                }
-               
+
                if (!Double.isNaN(cpAOA)) {
                        conditions.setAOA(cpAOA);
                } else {
                        conditions.setAOA(0);
                }
                extraText.setAOA(cpAOA);
-               
+
                if (!Double.isNaN(cpRoll)) {
                        conditions.setRollRate(cpRoll);
                } else {
                        conditions.setRollRate(0);
                }
-               
+
                if (!Double.isNaN(cpTheta)) {
                        conditions.setTheta(cpTheta);
                        cp = aerodynamicCalculator.getCP(configuration, conditions, warnings);
@@ -502,21 +596,24 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        cp = aerodynamicCalculator.getWorstCP(configuration, conditions, warnings);
                }
                extraText.setTheta(cpTheta);
-               
+
 
                cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS);
                //              System.out.println("CG computed as "+cg+ " CP as "+cp);
-               
+
                if (cp.weight > 0.000001)
                        cpx = cp.x;
                else
                        cpx = Double.NaN;
-               
+
                if (cg.weight > 0.000001)
                        cgx = cg.x;
                else
                        cgx = Double.NaN;
-               
+
+               figure3d.setCG(cg);
+               figure3d.setCP(cp);
+
                // Length bound is assumed to be tight
                double length = 0, diameter = 0;
                Collection<Coordinate> bounds = configuration.getBounds();
@@ -530,7 +627,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        }
                        length = maxX - minX;
                }
-               
+
                for (RocketComponent c : configuration) {
                        if (c instanceof SymmetricComponent) {
                                double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
@@ -538,31 +635,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                                diameter = MathUtil.max(diameter, d1, d2);
                        }
                }
-               
+
                extraText.setCG(cgx);
                extraText.setCP(cpx);
                extraText.setLength(length);
                extraText.setDiameter(diameter);
                extraText.setMass(cg.weight);
                extraText.setWarnings(warnings);
-               
+
 
                if (figure.getType() == RocketFigure.TYPE_SIDE && length > 0) {
-                       
+
                        // TODO: LOW: Y-coordinate and rotation
                        extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0);
                        extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0);
-                       
+
                } else {
-                       
+
                        extraCP.setPosition(Double.NaN, Double.NaN);
                        extraCG.setPosition(Double.NaN, Double.NaN);
-                       
+
                }
-               
+
 
                ////////  Flight simulation in background
-               
+
                // Check whether to compute or not
                if (!((SwingPreferences) Application.getPreferences()).computeFlightInBackground()) {
                        extraText.setFlightData(null);
@@ -570,38 +667,38 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        stopBackgroundSimulation();
                        return;
                }
-               
+
                // Check whether data is already up to date
                if (flightDataFunctionalID == configuration.getRocket().getFunctionalModID() &&
                                flightDataMotorID == configuration.getMotorConfigurationID()) {
                        return;
                }
-               
+
                flightDataFunctionalID = configuration.getRocket().getFunctionalModID();
                flightDataMotorID = configuration.getMotorConfigurationID();
-               
+
                // Stop previous computation (if any)
                stopBackgroundSimulation();
-               
+
                // Check that configuration has motors
                if (!configuration.hasMotors()) {
                        extraText.setFlightData(FlightData.NaN_DATA);
                        extraText.setCalculatingData(false);
                        return;
                }
-               
+
                // Start calculation process
                extraText.setCalculatingData(true);
-               
+
                Rocket duplicate = (Rocket) configuration.getRocket().copy();
                Simulation simulation = ((SwingPreferences)Application.getPreferences()).getBackgroundSimulation(duplicate);
                simulation.getOptions().setMotorConfigurationID(
                                configuration.getMotorConfigurationID());
-               
-               backgroundSimulationWorker = new BackgroundSimulationWorker(simulation);
+
+               backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation);
                backgroundSimulationExecutor.execute(backgroundSimulationWorker);
        }
-       
+
        /**
         * Cancels the current background simulation worker, if any.
         */
@@ -611,22 +708,26 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        backgroundSimulationWorker = null;
                }
        }
-       
-       
+
+
        /**
         * A SimulationWorker that simulates the rocket flight in the background and
         * sets the results to the extra text when finished.  The worker can be cancelled
         * if necessary.
         */
        private class BackgroundSimulationWorker extends SimulationWorker {
-               
-               public BackgroundSimulationWorker(Simulation sim) {
+
+               private final CustomExpressionSimulationListener exprListener;
+
+               public BackgroundSimulationWorker(OpenRocketDocument doc, Simulation sim) {
                        super(sim);
+                       List<CustomExpression> exprs = doc.getCustomExpressions();
+                       exprListener = new CustomExpressionSimulationListener(exprs);
                }
-               
+
                @Override
                protected FlightData doInBackground() {
-                       
+
                        // Pause a little while to allow faster UI reaction
                        try {
                                Thread.sleep(300);
@@ -634,59 +735,71 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        }
                        if (isCancelled() || backgroundSimulationWorker != this)
                                return null;
-                       
+
                        return super.doInBackground();
                }
-               
+
                @Override
                protected void simulationDone() {
                        // Do nothing if cancelled
                        if (isCancelled() || backgroundSimulationWorker != this)
                                return;
-                       
+
                        backgroundSimulationWorker = null;
                        extraText.setFlightData(simulation.getSimulatedData());
                        extraText.setCalculatingData(false);
                        figure.repaint();
+                       figure3d.repaint();
                }
-               
+
                @Override
                protected SimulationListener[] getExtraListeners() {
                        return new SimulationListener[] {
                                        InterruptListener.INSTANCE,
-                                       ApogeeEndListener.INSTANCE };
+                                       ApogeeEndListener.INSTANCE,
+                                       exprListener};
+
                }
-               
+
                @Override
                protected void simulationInterrupted(Throwable t) {
                        // Do nothing on cancel, set N/A data otherwise
                        if (isCancelled() || backgroundSimulationWorker != this) // Double-check
                                return;
-                       
+
                        backgroundSimulationWorker = null;
                        extraText.setFlightData(FlightData.NaN_DATA);
                        extraText.setCalculatingData(false);
                        figure.repaint();
+                       figure3d.repaint();
                }
        }
-       
-       
+
+
 
        /**
         * Adds the extra data to the figure.  Currently this includes the CP and CG carets.
         */
        private void addExtras() {
-               figure.clearRelativeExtra();
                extraCG = new CGCaret(0, 0);
                extraCP = new CPCaret(0, 0);
                extraText = new RocketInfo(configuration);
                updateExtras();
+
+               figure.clearRelativeExtra();
                figure.addRelativeExtra(extraCP);
                figure.addRelativeExtra(extraCG);
                figure.addAbsoluteExtra(extraText);
+
+
+               figure3d.clearRelativeExtra();
+               //figure3d.addRelativeExtra(extraCP);
+               //figure3d.addRelativeExtra(extraCG);
+               figure3d.addAbsoluteExtra(extraText);
+
        }
-       
-       
+
+
        /**
         * Updates the selection in the FigureParameters and repaints the figure.  
         * Ignores the event itself.
@@ -698,14 +811,16 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        figure.setSelection(null);
                        return;
                }
-               
+
                RocketComponent[] components = new RocketComponent[paths.length];
                for (int i = 0; i < paths.length; i++)
                        components[i] = (RocketComponent) paths[i].getLastPathComponent();
                figure.setSelection(components);
+
+               figure3d.setSelection(components);
        }
-       
-       
+
+
 
        /**
         * An <code>Action</code> that shows whether the figure type is the type
@@ -714,29 +829,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * @author Sampo Niskanen <sampo.niskanen@iki.fi>
         */
        private class FigureTypeAction extends AbstractAction implements StateChangeListener {
+               private static final long serialVersionUID = 1L;
                private final int type;
-               
+
                public FigureTypeAction(int type) {
                        this.type = type;
                        stateChanged(null);
                        figure.addChangeListener(this);
                }
-               
+
                @Override
                public void actionPerformed(ActionEvent e) {
                        boolean state = (Boolean) getValue(Action.SELECTED_KEY);
                        if (state == true) {
                                // This view has been selected
                                figure.setType(type);
+                               go2D();
                                updateExtras();
                        }
                        stateChanged(null);
                }
-               
+
                @Override
                public void stateChanged(EventObject e) {
-                       putValue(Action.SELECTED_KEY, figure.getType() == type);
+                       putValue(Action.SELECTED_KEY, figure.getType() == type && !is3d);
                }
        }
-       
+
 }