From a6ae9a47c72b215a44792ff4883ad1668bbc6abb Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Nov 2022 13:16:05 +0100 Subject: [PATCH 01/22] [#1815] Don't let preset loading override component name on file import --- .../file/openrocket/importt/ComponentPresetSetter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java index 8a10be1d7..b90940629 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java @@ -68,7 +68,10 @@ class ComponentPresetSetter implements Setter { if (digest != null && !matchingPreset.getDigest().equals(digest)) { warnings.add(Warning.fromString("ComponentPreset for component " + c.getName() + " has wrong digest")); } - + + // The preset loader can override the component name, so first store it and then apply it again + String componentName = c.getName(); setMethod.invoke(c, matchingPreset); + c.setName(componentName); } } From bd66055e4a0753f118fb9623291cdd3eb88c27de Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 13 Nov 2022 23:49:39 +0100 Subject: [PATCH 02/22] [#1817] Create zero-argument constructor for StopSimulationListener --- .../listeners/example/StopSimulationListener.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/simulation/listeners/example/StopSimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/example/StopSimulationListener.java index e179d2da9..a76461696 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/example/StopSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/example/StopSimulationListener.java @@ -22,7 +22,11 @@ public class StopSimulationListener extends AbstractSimulationListener { private long startTime = -1; private long time = -1; - + + public StopSimulationListener() { + this(0, 0); + } + public StopSimulationListener(double t, int n) { stopTime = t; stopStep = n; From df675f5366d8090773690dd85b5cc6ec770c4663 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Sun, 13 Nov 2022 15:57:33 -0700 Subject: [PATCH 03/22] Provide user with warning when a simulation branch contains no data Also, add a method to WarningSet making it easy to add a "discriminator" to warnings, informing the user what in a design or simulation is causing the warning. --- core/resources/l10n/messages.properties | 1 + core/src/net/sf/openrocket/aerodynamics/Warning.java | 3 ++- .../net/sf/openrocket/aerodynamics/WarningSet.java | 12 +++++++++++- .../simulation/BasicEventSimulationEngine.java | 6 ++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index c5929d0bb..7409e4c38 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1835,6 +1835,7 @@ Warning.ZERO_LENGTH_BODY = Zero length bodies may not result in accurate simulat Warning.ZERO_RADIUS_BODY = Zero length bodies may not result in accurate simulations. Warning.TUBE_SEPARATION = Space between tube fins may not result in accurate simulations. Warning.TUBE_OVERLAP = Overlapping tube fins may not result in accurate simulations. +Warning.EMPTY_BRANCH = Simulation branch contains no data Warning.SEPARATION_ORDER = Stages separated in an unreasonable order ! Scale dialog diff --git a/core/src/net/sf/openrocket/aerodynamics/Warning.java b/core/src/net/sf/openrocket/aerodynamics/Warning.java index 803073948..ae7c94e35 100644 --- a/core/src/net/sf/openrocket/aerodynamics/Warning.java +++ b/core/src/net/sf/openrocket/aerodynamics/Warning.java @@ -18,7 +18,6 @@ public abstract class Warning { return new Warning.Other(text); } - /** * Return true if the other warning should replace * this warning. The method should return true if the other @@ -398,4 +397,6 @@ public abstract class Warning { public static final Warning TUBE_OVERLAP = new Other(trans.get("Warning.TUBE_OVERLAP")); public static final Warning SEPARATION_ORDER = new Other(trans.get("Warning.SEPARATION_ORDER")); + + public static final Warning EMPTY_BRANCH = new Other(trans.get("Warning.EMPTY_BRANCH")); } diff --git a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java b/core/src/net/sf/openrocket/aerodynamics/WarningSet.java index 4873976f3..6f709e83b 100644 --- a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java +++ b/core/src/net/sf/openrocket/aerodynamics/WarningSet.java @@ -67,7 +67,17 @@ public class WarningSet extends AbstractSet implements Cloneable, Monit mutable.check(); return add(Warning.fromString(s)); } - + + /** + * Add a Warning of the specified type with the specified discriminator to the + * set. + * @param w the warning + * @param d the extra discriminator + * + */ + public boolean add (Warning w, String d) { + return this.add(w.toString() + ": " + d); + } @Override public Iterator iterator() { diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 339c0704b..d3d61b829 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -97,6 +97,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { dataBranch.getBranchName(), currentStatus.getSimulationTime(), dataBranch.getLast(FlightDataType.TYPE_TIME))); + + + // Did the branch generate any data? + if (dataBranch.getLength() == 0) { + flightData.getWarningSet().add(Warning.EMPTY_BRANCH, dataBranch.getBranchName()); + } }while( ! toSimulate.isEmpty()); SimulationListenerHelper.fireEndSimulation(currentStatus, null); From c5b0271db77d31cbaa31aeae8a860d316e436570 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 14 Nov 2022 13:49:44 +0100 Subject: [PATCH 04/22] [#1817] Add extra error information on simulation listener instantiation --- core/resources/l10n/messages.properties | 2 ++ .../simulation/extension/impl/JavaCode.java | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 0c13b37f3..d1462bf8a 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -470,6 +470,8 @@ SimulationExtension.javacode.name = Java code SimulationExtension.javacode.name.none = none SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation SimulationExtension.javacode.className = Fully-qualified Java class name: +SimulationExtension.javacode.classnotfound = Could not find class +SimulationExtension.javacode.couldnotinstantiate = Could not instantiate class %s.
Does it have a zero-argument, or @Inject constructor? SimulationExtension.scripting.name = {language} script SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts. diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/JavaCode.java b/core/src/net/sf/openrocket/simulation/extension/impl/JavaCode.java index 3602ec175..8a1acfbfa 100644 --- a/core/src/net/sf/openrocket/simulation/extension/impl/JavaCode.java +++ b/core/src/net/sf/openrocket/simulation/extension/impl/JavaCode.java @@ -1,5 +1,6 @@ package net.sf.openrocket.simulation.extension.impl; +import com.google.inject.ConfigurationException; import net.sf.openrocket.simulation.SimulationConditions; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.extension.AbstractSimulationExtension; @@ -23,11 +24,15 @@ public class JavaCode extends AbstractSimulationExtension { if (!SimulationListener.class.isAssignableFrom(clazz)) { throw new SimulationException("Class " + className + " does not implement SimulationListener"); } - SimulationListener listener = (SimulationListener) injector.getInstance(clazz); - conditions.getSimulationListenerList().add(listener); + try { + SimulationListener listener = (SimulationListener) injector.getInstance(clazz); + conditions.getSimulationListenerList().add(listener); + } catch (ConfigurationException e) { + throw new SimulationException(String.format(trans.get("SimulationExtension.javacode.couldnotinstantiate"), className), e); + } } } catch (ClassNotFoundException e) { - throw new SimulationException("Could not find class " + className); + throw new SimulationException(trans.get("SimulationExtension.javacode.classnotfound") + " " + className); } } From 1021bbc8a9e58910813570ec3a188500e7b9ec0e Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 14 Nov 2022 13:56:56 +0100 Subject: [PATCH 05/22] [#1819] Update extensions after configurator instantiation --- .../sf/openrocket/gui/simulation/SimulationOptionsPanel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java index 0ee297b2b..75673e6aa 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java @@ -234,6 +234,7 @@ class SimulationOptionsPanel extends JPanel { SwingSimulationExtensionConfigurator configurator = findConfigurator(e); if (configurator != null) { configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this)); + updateCurrentExtensions(); } } }); @@ -257,6 +258,7 @@ class SimulationOptionsPanel extends JPanel { SwingSimulationExtensionConfigurator configurator = findConfigurator(e); if (configurator != null) { configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this)); + updateCurrentExtensions(); } } }); From fa27fd0b9a3af007243dd9331e855d5f3abd6476 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 14 Nov 2022 13:57:56 +0100 Subject: [PATCH 06/22] Add warning text if simulation listener not recognized as class --- ...tSwingSimulationExtensionConfigurator.java | 22 ++++++--- .../extension/impl/JavaCodeConfigurator.java | 47 +++++++++++++++++-- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java b/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java index 422c87ea6..b0a1992e7 100644 --- a/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java +++ b/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java @@ -4,6 +4,8 @@ import java.awt.Dialog.ModalityType; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JComponent; @@ -50,17 +52,21 @@ public abstract class AbstractSwingSimulationExtensionConfigurator { - + private JavaCode extension; + private JTextField classNameField; + + private static final Translator trans = Application.getTranslator(); + public JavaCodeConfigurator() { super(JavaCode.class); } @Override protected JComponent getConfigurationComponent(final JavaCode extension, Simulation simulation, JPanel panel) { + this.extension = extension; panel.add(new JLabel(trans.get("SimulationExtension.javacode.desc")), "wrap para"); panel.add(new JLabel(trans.get("SimulationExtension.javacode.className")), "wrap rel"); - final JTextField textField = new JTextField(extension.getClassName()); - textField.getDocument().addDocumentListener(new DocumentListener() { + classNameField = new JTextField(extension.getClassName()); + panel.add(classNameField, "growx, wrap"); + StyledLabel errorMsg = new StyledLabel(); + errorMsg.setFontColor(Color.DARK_RED.toAWTColor()); + errorMsg.setVisible(false); + panel.add(errorMsg, "growx, wrap"); + + classNameField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { update(); } @@ -37,11 +52,33 @@ public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfig } public void update() { - extension.setClassName(textField.getText()); + // Display error message if the class name is invalid + String text = classNameField.getText().trim(); + try { + Class.forName(text); + errorMsg.setVisible(false); + } catch (ClassNotFoundException e) { + // Don't display an error message for an empty field + if (text.length() == 0) { + errorMsg.setVisible(false); + return; + } + errorMsg.setText(trans.get("SimulationExtension.javacode.classnotfound")); + errorMsg.setVisible(true); + } } }); - panel.add(textField, "growx"); + return panel; } + + @Override + protected void close() { + if (this.extension != null && this.classNameField != null) { + this.extension.setClassName(this.classNameField.getText().trim()); + + } + super.close(); + } } From f68189c3e2b4fbe4f018b1ab297238659e169405 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 14 Nov 2022 14:12:58 +0100 Subject: [PATCH 07/22] Delete unused import --- swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java | 1 - 1 file changed, 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java index e80cd4735..1714fbd6f 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java @@ -25,7 +25,6 @@ import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; -import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LinearInterpolator; import net.sf.openrocket.utils.DecimalFormatter; From baed200d27de6028f9e7e1bd8f182c30eaf09b25 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 14 Nov 2022 16:42:06 +0100 Subject: [PATCH 08/22] [#1820] Don't switch to simulation plot if "Simulate & Plot" resulted in faulty simulation --- .../gui/simulation/SimulationEditDialog.java | 8 +++----- .../gui/simulation/SimulationRunDialog.java | 19 +++++++++++++++++-- .../gui/simulation/SimulationWorker.java | 11 +++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java index 7955bdae3..287182c92 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java @@ -209,12 +209,10 @@ public class SimulationEditDialog extends JDialog { @Override public void actionPerformed(ActionEvent e) { copyChangesToAllSims(); - SimulationRunDialog.runSimulations(parentWindow, SimulationEditDialog.this.document, simulationList); - refreshView(); - if (allowsPlotMode()) { + SimulationRunDialog dialog = SimulationRunDialog.runSimulations(parentWindow, SimulationEditDialog.this.document, simulationList); + if (allowsPlotMode() && dialog.isAllSimulationsSuccessful()) { + refreshView(); setPlotMode(); - } else { - setVisible(false); } } }); diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java index 44416bf24..22b4cb2c3 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java @@ -207,9 +207,12 @@ public class SimulationRunDialog extends JDialog { * the parent Window of the dialog to use. * @param simulations * the simulations to run. + * @return the simulation run dialog instance. */ - public static void runSimulations(Window parent, OpenRocketDocument document, Simulation... simulations) { - new SimulationRunDialog(parent, document, simulations).setVisible(true); + public static SimulationRunDialog runSimulations(Window parent, OpenRocketDocument document, Simulation... simulations) { + SimulationRunDialog dialog = new SimulationRunDialog(parent, document, simulations); + dialog.setVisible(true); + return dialog; } private void updateProgress() { @@ -257,6 +260,18 @@ public class SimulationRunDialog extends JDialog { + u.toStringUnit(simulationMaxVelocity[index]) + ")"); } + /** + * Returns true if all the simulations ran successfully. Returns false if the simulations encountered + * an exception, or were cancelled. + */ + public boolean isAllSimulationsSuccessful() { + for (SimulationWorker w : simulationWorkers) { + if (w.getThrowable() != null) + return false; + } + return true; + } + /** * A SwingWorker that performs a flight simulation. It periodically updates * the simulation statuses of the parent class and calls updateProgress(). diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java index 201b4f22e..1fb6b682b 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java @@ -70,6 +70,17 @@ public abstract class SimulationWorker extends SwingWorker Date: Tue, 15 Nov 2022 11:52:35 +0100 Subject: [PATCH 09/22] Make sure error message updates at start-up --- .../extension/impl/JavaCodeConfigurator.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/swing/src/net/sf/openrocket/simulation/extension/impl/JavaCodeConfigurator.java b/swing/src/net/sf/openrocket/simulation/extension/impl/JavaCodeConfigurator.java index ee3e8ca71..ba9fe43b6 100644 --- a/swing/src/net/sf/openrocket/simulation/extension/impl/JavaCodeConfigurator.java +++ b/swing/src/net/sf/openrocket/simulation/extension/impl/JavaCodeConfigurator.java @@ -19,6 +19,7 @@ import net.sf.openrocket.util.Color; public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfigurator { private JavaCode extension; private JTextField classNameField; + private StyledLabel errorMsg; private static final Translator trans = Application.getTranslator(); @@ -33,7 +34,7 @@ public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfig panel.add(new JLabel(trans.get("SimulationExtension.javacode.className")), "wrap rel"); classNameField = new JTextField(extension.getClassName()); panel.add(classNameField, "growx, wrap"); - StyledLabel errorMsg = new StyledLabel(); + this.errorMsg = new StyledLabel(); errorMsg.setFontColor(Color.DARK_RED.toAWTColor()); errorMsg.setVisible(false); panel.add(errorMsg, "growx, wrap"); @@ -52,26 +53,34 @@ public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfig } public void update() { - // Display error message if the class name is invalid - String text = classNameField.getText().trim(); - try { - Class.forName(text); - errorMsg.setVisible(false); - } catch (ClassNotFoundException e) { - // Don't display an error message for an empty field - if (text.length() == 0) { - errorMsg.setVisible(false); - return; - } - errorMsg.setText(trans.get("SimulationExtension.javacode.classnotfound")); - errorMsg.setVisible(true); - } + updateErrorMsg(); } }); + updateErrorMsg(); return panel; } + private void updateErrorMsg() { + if (this.errorMsg == null) { + return; + } + // Display error message if the class name is invalid + String text = classNameField.getText().trim(); + try { + Class.forName(text); + errorMsg.setVisible(false); + } catch (ClassNotFoundException e) { + // Don't display an error message for an empty field + if (text.length() == 0) { + errorMsg.setVisible(false); + return; + } + errorMsg.setText(trans.get("SimulationExtension.javacode.classnotfound")); + errorMsg.setVisible(true); + } + } + @Override protected void close() { if (this.extension != null && this.classNameField != null) { From 451d9ea00961ca6542e56faa2f20e9f4db03029d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 15 Nov 2022 14:24:54 +0100 Subject: [PATCH 10/22] [#1824] Fire document update when simulation is done This updates the sim table --- .../sf/openrocket/gui/simulation/SimulationRunDialog.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java index 44416bf24..3288c753a 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java @@ -23,6 +23,8 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; +import net.sf.openrocket.document.events.DocumentChangeEvent; +import net.sf.openrocket.document.events.SimulationChangeEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -273,6 +275,7 @@ public class SimulationRunDialog extends JDialog { private volatile double apogeeAltitude; private final CustomExpressionSimulationListener exprListener; + private final OpenRocketDocument document; /* * Keep track of current phase ("stage") of simulation @@ -287,7 +290,8 @@ public class SimulationRunDialog extends JDialog { public InteractiveSimulationWorker(OpenRocketDocument doc, Simulation sim, int index) { super(sim); - List exprs = doc.getCustomExpressions(); + this.document = doc; + List exprs = document.getCustomExpressions(); exprListener = new CustomExpressionSimulationListener(exprs); this.index = index; @@ -389,6 +393,7 @@ public class SimulationRunDialog extends JDialog { log.debug("Simulation done"); setSimulationProgress(1.0); updateProgress(); + document.fireDocumentChangeEvent(new SimulationChangeEvent(simulation)); } /** From f3b8459f370010935610f9d0eff609f36b6aaa3d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 15 Nov 2022 14:36:33 +0100 Subject: [PATCH 11/22] Remove unused imports and wildcard imports --- .../sf/openrocket/gui/main/BasicFrame.java | 24 +++++++++++++++++-- .../gui/scalefigure/RocketPanel.java | 2 -- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index de604e585..685560786 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1,6 +1,8 @@ package net.sf.openrocket.gui.main; -import java.awt.*; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -20,7 +22,25 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; -import javax.swing.*; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; import javax.swing.border.BevelBorder; import javax.swing.event.ChangeEvent; import javax.swing.tree.DefaultTreeSelectionModel; diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 567495c0c..5a61fd46e 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -5,7 +5,6 @@ import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; -import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; @@ -16,7 +15,6 @@ import java.util.EventListener; import java.util.EventObject; import java.util.List; import java.util.LinkedList; -import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; From 9d24cecbef4c2b13fd938d4ca323d4e3db39a915 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Tue, 15 Nov 2022 09:46:05 -0700 Subject: [PATCH 12/22] Move recovery device check to simulation startup. It doesn't need to be checked on every iteration. --- .../simulation/BasicEventSimulationEngine.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 339c0704b..38e5c487b 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -73,6 +73,14 @@ public class BasicEventSimulationEngine implements SimulationEngine { } currentStatus = new SimulationStatus(simulationConfig, simulationConditions); + + // Sanity checks on design and configuration + + // No recovery device + if (!currentStatus.getConfiguration().hasRecoveryDevice()) { + currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE); + } + currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket())); { // main simulation branch @@ -341,11 +349,6 @@ public class BasicEventSimulationEngine implements SimulationEngine { event.getTime() + Math.max(0.001, deployConfig.getDeployDelay()), c)); } } - - // Add a warning if there is no recovery device defined. - if (!currentStatus.getConfiguration().hasRecoveryDevice()) { - currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE); - } // Handle event log.trace("Handling event " + event); From 21987ce411476f70402a3a7e94043a881512abca Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Tue, 15 Nov 2022 10:43:00 -0700 Subject: [PATCH 13/22] Make sanity check code at start of simulation clearer On SimulationException, record the abort in the flight data but then rethrow the exception --- core/resources/l10n/messages.properties | 1 + .../BasicEventSimulationEngine.java | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index d1462bf8a..864d21d9c 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -554,6 +554,7 @@ SimuRunDlg.msg.errorOccurred = An error occurred during the simulation: BasicEventSimulationEngine.error.noMotorsDefined = No motors defined in the simulation. BasicEventSimulationEngine.error.earlyMotorBurnout = Motor burnout without liftoff. +BasicEventSimulationEngine.error.noConfiguredIgnition = No motors configured to ignite at liftoff BasicEventSimulationEngine.error.noIgnition = No motors ignited. BasicEventSimulationEngine.error.NaNResult = Simulation resulted in not-a-number (NaN) value, please report a bug. diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 38e5c487b..59c4fe25d 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -1,6 +1,7 @@ package net.sf.openrocket.simulation; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Deque; import org.slf4j.Logger; @@ -8,6 +9,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationId; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -68,16 +70,22 @@ public class BasicEventSimulationEngine implements SimulationEngine { FlightConfiguration origConfig = simulationConditions.getRocket().getFlightConfiguration(this.fcid); FlightConfiguration simulationConfig = origConfig.clone(); simulationConfig.copyStages(origConfig); // Clone the stage activation configuration - if ( ! simulationConfig.hasMotors() ) { - throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined")); - } currentStatus = new SimulationStatus(simulationConfig, simulationConditions); // Sanity checks on design and configuration - + + // Problems that keep us from simulating at all + + // No motors in configuration + if (!simulationConfig.hasMotors() ) { + throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined")); + } + + // Problems that let us simulate, but result is likely bad + // No recovery device - if (!currentStatus.getConfiguration().hasRecoveryDevice()) { + if (!simulationConfig.hasRecoveryDevice()) { currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE); } @@ -116,7 +124,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { return flightData; } - private FlightDataBranch simulateLoop() { + private FlightDataBranch simulateLoop() throws SimulationException { // Initialize the simulation. We'll use the flight stepper unless we're already on the ground if (currentStatus.isLanded()) @@ -248,9 +256,11 @@ public class BasicEventSimulationEngine implements SimulationEngine { } catch (SimulationException e) { SimulationListenerHelper.fireEndSimulation(currentStatus, e); + // Add FlightEvent for Abort. currentStatus.getFlightData().addEvent(new FlightEvent(FlightEvent.Type.EXCEPTION, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), e.getLocalizedMessage())); - currentStatus.getWarnings().add(e.getLocalizedMessage()); + + throw e; } return currentStatus.getFlightData(); From c79a75f9bb683337868fe8d7773ceeb5665982aa Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 15 Nov 2022 23:14:47 +0100 Subject: [PATCH 14/22] [#1831] Don't highlight sustainer stage at startup --- .../sf/openrocket/gui/main/BasicFrame.java | 4 +- .../sf/openrocket/gui/main/DesignPanel.java | 73 +++++++++++++------ 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 685560786..8134f1fb7 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -279,11 +279,13 @@ public class BasicFrame extends JFrame { if( componentSelectionModel.isSelectionEmpty() ){ final Rocket rocket = document.getRocket(); if( rocket != null ) { - final AxialStage topStage = (AxialStage) rocket.getChild(0); + final RocketComponent topStage = rocket.getChild(0); if (topStage != null) { final TreePath selectionPath = new TreePath(topStage); componentSelectionModel.setSelectionPath(selectionPath); tree.setSelectionRow(1); + // Don't select children components at startup (so override the default behavior with this new selection) + rocketpanel.getFigure().setSelection(new RocketComponent[] { topStage }); log.debug("... Setting Initial Selection: " + tree.getSelectionPath() ); } } diff --git a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java index 9795207e7..dcb300261 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java @@ -69,36 +69,30 @@ public class DesignPanel extends JSplitPane { im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_KEY), null); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, SHORTCUT_KEY), null); - // Visually select all child components of a stage/rocket/podset when it is selected + // Highlight all child components of a stage/rocket/podset when it is selected tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { - if (tree == null || tree.getSelectionPaths() == null || tree.getSelectionPaths().length == 0 - || parent.getRocketPanel() == null) return; - - // Get all the components that need to be selected = currently selected components + children of stages/boosters/podsets - List children = new ArrayList<>(Arrays.asList(parent.getRocketPanel().getFigure().getSelection())); - for (TreePath p : tree.getSelectionPaths()) { - if (p != null) { - RocketComponent c = (RocketComponent) p.getLastPathComponent(); - if (c instanceof AxialStage || c instanceof Rocket || c instanceof PodSet) { - Iterator iter = c.iterator(false); - while (iter.hasNext()) { - RocketComponent child = iter.next(); - children.add(child); - } - } - } - } - - // Select all the child components - if (parent.getRocketPanel().getFigure() != null && parent.getRocketPanel().getFigure3d() != null) { - parent.getRocketPanel().getFigure().setSelection(children.toArray(new RocketComponent[0])); - parent.getRocketPanel().getFigure3d().setSelection(children.toArray(new RocketComponent[0])); - } + highlightAssemblyChildren(tree, parent); } }); + // Add a mouse listener for when the sustainer is selected at startup, to ensure that its children are highlighted. + // This is necessary because we force the children to not be highlighted when the tree is first created, and + // re-clicking the sustainer would not fire a change event in the tree (which normally highlights the children). + MouseAdapter mouseAdapter = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (tree.getSelectionPath() != null && + tree.getSelectionPath().getLastPathComponent() == document.getRocket().getChild(0)) { + highlightAssemblyChildren(tree, parent); + } + // Delete the listener again. We only need it at start-up, i.e. when the first click is registered. + tree.removeMouseListener(this); + } + }; + tree.addMouseListener(mouseAdapter); + // Double-click opens config dialog MouseListener ml = new MouseAdapter() { @Override @@ -234,6 +228,37 @@ public class DesignPanel extends JSplitPane { this.setRightComponent(panel); } + /** + * Highlight all child components of a stage/rocket/podset when it is selected + * @param tree the tree in which the component selection took place + * @param parent the parent frame to highlight the components in + */ + private static void highlightAssemblyChildren(ComponentTree tree, BasicFrame parent) { + if (tree == null || tree.getSelectionPaths() == null || tree.getSelectionPaths().length == 0 + || parent.getRocketPanel() == null) return; + + // Get all the components that need to be selected = currently selected components + children of stages/boosters/podsets + List children = new ArrayList<>(Arrays.asList(parent.getRocketPanel().getFigure().getSelection())); + for (TreePath p : tree.getSelectionPaths()) { + if (p != null) { + RocketComponent c = (RocketComponent) p.getLastPathComponent(); + if (c instanceof AxialStage || c instanceof Rocket || c instanceof PodSet) { + Iterator iter = c.iterator(false); + while (iter.hasNext()) { + RocketComponent child = iter.next(); + children.add(child); + } + } + } + } + + // Select all the child components + if (parent.getRocketPanel().getFigure() != null && parent.getRocketPanel().getFigure3d() != null) { + parent.getRocketPanel().getFigure().setSelection(children.toArray(new RocketComponent[0])); + parent.getRocketPanel().getFigure3d().setSelection(children.toArray(new RocketComponent[0])); + } + } + /** * Focus on the component tree. */ From 7c57c272fb7270989a1b359fdbda552bbfc14ad4 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Wed, 16 Nov 2022 09:36:48 -0700 Subject: [PATCH 15/22] Tell user where discontinuity exists in body tube --- core/resources/l10n/messages.properties | 2 +- core/resources/l10n/messages_cs.properties | 2 +- core/resources/l10n/messages_es.properties | 2 +- core/resources/l10n/messages_fr.properties | 2 +- core/resources/l10n/messages_it.properties | 2 +- core/resources/l10n/messages_nl.properties | 2 +- core/resources/l10n/messages_pl.properties | 2 +- core/resources/l10n/messages_pt.properties | 2 +- core/resources/l10n/messages_uk_UA.properties | 2 +- core/resources/l10n/messages_zh_CN.properties | 2 +- .../aerodynamics/AerodynamicCalculator.java | 2 -- .../aerodynamics/BarrowmanCalculator.java | 21 ++++++------------- 12 files changed, 16 insertions(+), 27 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 097daf782..328713a3a 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1829,7 +1829,7 @@ PlotConfiguration.Groundtrack = Ground track ! Warning Warning.LargeAOA.str1 = Large angle of attack encountered. Warning.LargeAOA.str2 = Large angle of attack encountered ( -Warning.DISCONTINUITY = Discontinuity in rocket body diameter. +Warning.DISCONTINUITY = Discontinuity in rocket body diameter Warning.THICK_FIN = Thick fins may not be modeled accurately. Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate. Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation diff --git a/core/resources/l10n/messages_cs.properties b/core/resources/l10n/messages_cs.properties index 619f8419d..48cb441fd 100644 --- a/core/resources/l10n/messages_cs.properties +++ b/core/resources/l10n/messages_cs.properties @@ -1315,7 +1315,7 @@ PlotConfiguration.Simulationtime = Simulacn ! Warning Warning.LargeAOA.str1 = Velký úhel nábehu. Warning.LargeAOA.str2 = Velký úhel nábehu ( -Warning.DISCONTINUITY = Nespojitost v prumeru tela rakety. +Warning.DISCONTINUITY = Nespojitost v prumeru tela rakety Warning.THICK_FIN = Tlou\u0161tka stabilizátoru se nemu\u017Ee modelovat presne. Warning.JAGGED_EDGED_FIN = Zubaté hrany stabilizátoru mohou být vypocteny nepresne. Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation diff --git a/core/resources/l10n/messages_es.properties b/core/resources/l10n/messages_es.properties index 48a1df482..92cf2afc9 100644 --- a/core/resources/l10n/messages_es.properties +++ b/core/resources/l10n/messages_es.properties @@ -1225,7 +1225,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propiedades generales ! TubeCoupler TubeCoupler.TubeCoupler = Acoplador -Warning.DISCONTINUITY = Discontinuidad en el di\u00e1metro del fuselaje. +Warning.DISCONTINUITY = Discontinuidad en el di\u00e1metro del fuselaje Warning.FILE_INVALID_PARAMETER = Par\u00e1metro encontrado no v\u00e1lido, ignorado. Warning.JAGGED_EDGED_FIN = El perfil afilado de las aletas puede ser inexacto. Warning.LISTENERS_AFFECTED = Las Extensiones se ejecutaron con la simulaci\u00f3n del vuelo diff --git a/core/resources/l10n/messages_fr.properties b/core/resources/l10n/messages_fr.properties index 8eed74a8f..015842dc5 100644 --- a/core/resources/l10n/messages_fr.properties +++ b/core/resources/l10n/messages_fr.properties @@ -1219,7 +1219,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propri\u00E9t\u00E9s g\u00E9n\u00E9ra ! TubeCoupler TubeCoupler.TubeCoupler = Coupleur de tube -Warning.DISCONTINUITY = Discontinuit\u00E9 dans le diam\u00E8tre du corps de la fus\u00E9e. +Warning.DISCONTINUITY = Discontinuit\u00E9 dans le diam\u00E8tre du corps de la fus\u00E9e Warning.FILE_INVALID_PARAMETER = Param\u00E8tre invalide rencontr\u00E9, ignorer. Warning.JAGGED_EDGED_FIN = Des ailerons aux bords irr\u00E9guliers ne seront pas mod\u00E9lis\u00E9s correctement. Warning.LISTENERS_AFFECTED = Les \u00E9couteurs ont modifi\u00E9 la simulation de vol diff --git a/core/resources/l10n/messages_it.properties b/core/resources/l10n/messages_it.properties index 897a2f29b..96f4a35c3 100644 --- a/core/resources/l10n/messages_it.properties +++ b/core/resources/l10n/messages_it.properties @@ -1379,7 +1379,7 @@ PlotConfiguration.Simulationtime = Intervallo temporale della simulazione e temp ! Warning Warning.LargeAOA.str1 = Incontrato grande angolo d'attacco. Warning.LargeAOA.str2 = Incontrato grande angolo d'attacco ( -Warning.DISCONTINUITY = Discontinuita' nel diametro del tubo del corpo. +Warning.DISCONTINUITY = Discontinuita' nel diametro del tubo del corpo Warning.THICK_FIN = Le pinne sottili potrebbero non essere modellate in modo accurato. Warning.JAGGED_EDGED_FIN = Jagged-edged fin Le predizioni per le pinne potrebbero non essere accurate. Warning.LISTENERS_AFFECTED = Gli osservatori possono modificare le condizioni di simulazione diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties index 5d8668a29..c010211fc 100644 --- a/core/resources/l10n/messages_nl.properties +++ b/core/resources/l10n/messages_nl.properties @@ -1688,7 +1688,7 @@ PlotConfiguration.Groundtrack = Grondspoor ! Warning Warning.LargeAOA.str1 = Grote invalshoek aangetroffen. Warning.LargeAOA.str2 = Grote invalshoek aangetroffen ( -Warning.DISCONTINUITY = DiscontinuĂŻteit in raketromp diameter. +Warning.DISCONTINUITY = DiscontinuĂŻteit in raketromp diameter Warning.THICK_FIN = Dikke vinnen worden mogelijk niet nauwkeurig gemodelleerd. Warning.JAGGED_EDGED_FIN = De voorspellingen van gekartelde vinnen kunnen onnauwkeurig zijn. Warning.LISTENERS_AFFECTED = Luisteraars veranderden de vluchtsimulatie diff --git a/core/resources/l10n/messages_pl.properties b/core/resources/l10n/messages_pl.properties index 3b5bd6b0e..f65d3dda8 100644 --- a/core/resources/l10n/messages_pl.properties +++ b/core/resources/l10n/messages_pl.properties @@ -1319,7 +1319,7 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s. ! Warning Warning.LargeAOA.str1 = Wyst\u0105pi\u0142 du\u017Cy k\u0105t natarcia. Warning.LargeAOA.str2 = Wyst\u0105pi\u0142 du\u017Cy k\u0105t natarcia ( - Warning.DISCONTINUITY = Nieci\u0105g\u0142o\u015B\u0107 \u015Brednicy rakiety. + Warning.DISCONTINUITY = Nieci\u0105g\u0142o\u015B\u0107 \u015Brednicy rakiety Warning.THICK_FIN = Grube stateczniki mog\u0105 nie by\u0107 modelowane dok\u0142adnie. Warning.JAGGED_EDGED_FIN = Stateczniki o nieregularnych kraw\u0119dziach mog\u0105 zmniejszy\u0107 dok\u0142adno\u015B\u0107 prognoz. Warning.LISTENERS_AFFECTED = Detektory zmodyfikowa\u0142y symulacj\u0119 lotu diff --git a/core/resources/l10n/messages_pt.properties b/core/resources/l10n/messages_pt.properties index af3120d63..a4a3d1931 100644 --- a/core/resources/l10n/messages_pt.properties +++ b/core/resources/l10n/messages_pt.properties @@ -1182,7 +1182,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propriedades gerais # TubeCoupler TubeCoupler.TubeCoupler = Acoplador de tubo -Warning.DISCONTINUITY = Descontinuidade no di\u00e2metro do corpo do foguete. +Warning.DISCONTINUITY = Descontinuidade no di\u00e2metro do corpo do foguete Warning.FILE_INVALID_PARAMETER = Par\u00e2metro inv\u00e1lido encontrado, ignorando. Warning.JAGGED_EDGED_FIN = Previs\u00f5es com aletas de bordo irregular podem ser imprecisos. Warning.LISTENERS_AFFECTED = Observador modificou a simula\u00e7\u00e3o de voo diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties index 4dd2ea040..169171641 100644 --- a/core/resources/l10n/messages_uk_UA.properties +++ b/core/resources/l10n/messages_uk_UA.properties @@ -1535,7 +1535,7 @@ PlotConfiguration.Simulationtime = Simulation time step and computation time ! Warning Warning.LargeAOA.str1 = Large angle of attack encountered. Warning.LargeAOA.str2 = Large angle of attack encountered ( -Warning.DISCONTINUITY = Discontinuity in rocket body diameter. +Warning.DISCONTINUITY = Discontinuity in rocket body diameter Warning.THICK_FIN = Thick fins may not be modeled accurately. Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate. Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation diff --git a/core/resources/l10n/messages_zh_CN.properties b/core/resources/l10n/messages_zh_CN.properties index 03f619d04..2d3eed4ab 100644 --- a/core/resources/l10n/messages_zh_CN.properties +++ b/core/resources/l10n/messages_zh_CN.properties @@ -1299,7 +1299,7 @@ TubeFinSetCfg.lbl.Outerdiam = \u5916\u76F4\u5F84: TubeFinSetCfg.lbl.Thickness = \u539A\u5EA6: TubeFinSetCfg.lbl.ttip.Finrotation = \u7A33\u5B9A\u7FFC\u7EC4\u5408\u91CC\u7B2C\u4E00\u7247\u7684\u89D2\u5EA6 -Warning.DISCONTINUITY = \u7BAD\u4F53\u76F4\u5F84\u4E0D\u8FDE\u7EED. +Warning.DISCONTINUITY = \u7BAD\u4F53\u76F4\u5F84\u4E0D\u8FDE\u7EED Warning.FILE_INVALID_PARAMETER = \u65E0\u6548\u53C2\u6570, \u5FFD\u7565. Warning.JAGGED_EDGED_FIN = \u952F\u9F7F\u7FFC\u9884\u6D4B\u53EF\u80FD\u4E0D\u51C6\u786E. Warning.LISTENERS_AFFECTED = \u76D1\u542C\u5668\u4FEE\u6539\u4E86\u98DE\u884C\u4EFF\u771F diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index fbdac87e4..141f78f16 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -67,6 +67,4 @@ public interface AerodynamicCalculator extends Monitorable { * @return a new, independent instance of this aerodynamic calculator type */ public AerodynamicCalculator newInstance(); - - public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt); } diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index b85074405..80f33b99f 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -251,10 +251,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { if (calcMap == null) buildCalcMap(configuration); - - if (!isContinuous(configuration, configuration.getRocket())){ - warnings.add( Warning.DIAMETER_DISCONTINUITY); - } + + testIsContinuous(configuration, configuration.getRocket(), warnings); final InstanceMap imap = configuration.getActiveInstances(); @@ -277,12 +275,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { return assemblyForces; } - @Override - public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt){ - return testIsContinuous(configuration, rkt); - } - - private boolean testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot ){ + private void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){ Queue queue = new LinkedList<>(); for (RocketComponent child : treeRoot.getChildren()) { // Ignore inactive stages @@ -292,9 +285,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { queue.add(child); } - boolean isContinuous = true; SymmetricComponent prevComp = null; - while((isContinuous)&&( null != queue.peek())){ + while(null != queue.peek()) { RocketComponent comp = queue.poll(); if( comp instanceof SymmetricComponent ){ for (RocketComponent child : comp.getChildren()) { @@ -313,7 +305,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { // Check for radius discontinuity if ( !MathUtil.equals(sym.getForeRadius(), prevComp.getAftRadius())) { - isContinuous = false; + warnings.add( Warning.DIAMETER_DISCONTINUITY, sym + ", " + prevComp); } // double x = component.toAbsolute(Coordinate.NUL)[0].x; @@ -327,11 +319,10 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { prevComp = sym; }else if( comp instanceof ComponentAssembly ){ - isContinuous &= testIsContinuous(configuration, comp); + testIsContinuous(configuration, comp, warnings); } } - return isContinuous; } From a04fc455e972348cc4d24dcd6acc5e94eff4706f Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Wed, 16 Nov 2022 09:57:17 -0700 Subject: [PATCH 16/22] Update unit tests for modified continuity check Also, make testIsContinuous public in AerodynamicCalcutor, primarily for the benefit of unit tests. --- .../aerodynamics/AerodynamicCalculator.java | 5 +++++ .../aerodynamics/BarrowmanCalculator.java | 5 +++-- .../aerodynamics/BarrowmanCalculatorTest.java | 18 +++++++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index 141f78f16..9889a4987 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -67,4 +67,9 @@ public interface AerodynamicCalculator extends Monitorable { * @return a new, independent instance of this aerodynamic calculator type */ public AerodynamicCalculator newInstance(); + + /** + * Test component assembly for continuity (esp. diameter), and post any needed warnings + */ + public void testIsContinuous(FlightConfiguration configuration, final RocketComponent component, WarningSet warnings); } diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 80f33b99f..ac75e95a4 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -274,8 +274,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { return assemblyForces; } - - private void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){ + + @Override + public void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){ Queue queue = new LinkedList<>(); for (RocketComponent child : treeRoot.getChildren()) { // Ignore inactive stages diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 90d8044c0..d113cf003 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -288,8 +288,10 @@ public class BarrowmanCalculatorTest { Rocket rocket = TestRockets.makeEstesAlphaIII(); AerodynamicCalculator calc = new BarrowmanCalculator(); FlightConfiguration configuration = rocket.getSelectedConfiguration(); + WarningSet warnings = new WarningSet(); - assertTrue("Estes Alpha III should be continuous: ", calc.isContinuous(configuration, rocket)); + calc.testIsContinuous(configuration, rocket, warnings); + assertTrue("Estes Alpha III should be continuous: ", warnings.isEmpty()); } @Test @@ -297,8 +299,10 @@ public class BarrowmanCalculatorTest { Rocket rocket = TestRockets.makeFalcon9Heavy(); AerodynamicCalculator calc = new BarrowmanCalculator(); FlightConfiguration configuration = rocket.getSelectedConfiguration(); + WarningSet warnings = new WarningSet(); - assertTrue("F9H should be continuous: ", calc.isContinuous(configuration, rocket)); + calc.testIsContinuous(configuration, rocket, warnings); + assertTrue("F9H should be continuous: ", warnings.isEmpty()); } @Test @@ -306,6 +310,7 @@ public class BarrowmanCalculatorTest { Rocket rocket = TestRockets.makeEstesAlphaIII(); AerodynamicCalculator calc = new BarrowmanCalculator(); FlightConfiguration configuration = rocket.getSelectedConfiguration(); + WarningSet warnings = new WarningSet(); NoseCone nose = (NoseCone)rocket.getChild(0).getChild(0); BodyTube body = (BodyTube)rocket.getChild(0).getChild(1); @@ -313,8 +318,9 @@ public class BarrowmanCalculatorTest { nose.setAftRadius(0.015); body.setOuterRadius( 0.012 ); body.setName( body.getName()+" << discontinuous"); - - assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous(configuration, rocket)); + + calc.testIsContinuous(configuration, rocket, warnings); + assertFalse(" Estes Alpha III has an undetected discontinuity:", warnings.isEmpty()); } @Test @@ -322,6 +328,7 @@ public class BarrowmanCalculatorTest { Rocket rocket = TestRockets.makeFalcon9Heavy(); AerodynamicCalculator calc = new BarrowmanCalculator(); FlightConfiguration configuration = rocket.getSelectedConfiguration(); + WarningSet warnings = new WarningSet(); final AxialStage coreStage = (AxialStage)rocket.getChild(1); final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0); @@ -333,7 +340,8 @@ public class BarrowmanCalculatorTest { body.setOuterRadius( 0.012 ); body.setName( body.getName()+" << discontinuous"); - assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous(configuration, rocket)); + calc.testIsContinuous(configuration, rocket, warnings); + assertFalse(" Missed discontinuity in Falcon 9 Heavy:" , warnings.isEmpty()); } @Test From 0056c53640ee05f4daeb1581e27bbec84859d337 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 00:48:39 +0100 Subject: [PATCH 17/22] [#1829] Fix tesselation issue on fins with curved root --- .../sf/openrocket/rocketcomponent/FinSet.java | 2 +- .../gui/figure3d/geometry/FinRenderer.java | 65 +++++++++++-------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index a36ed0a93..45e0b2a1d 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -33,7 +33,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona * Maximum number of root points in the root geometry. */ private static final int MAX_ROOT_DIVISIONS = 100; - private static final int MAX_ROOT_DIVISIONS_LOW_RES = 15; + private static final int MAX_ROOT_DIVISIONS_LOW_RES = MAX_ROOT_DIVISIONS / 5; public void setOverrideMass() { } diff --git a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java index c4b1e2c4a..a8098964e 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java @@ -17,7 +17,7 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface; public class FinRenderer { - private GLUtessellator tobj = GLU.gluNewTess(); + private GLUtessellator tess = GLU.gluNewTess(); public void renderFinSet(final GL2 gl, FinSet finSet, Surface which) { @@ -50,7 +50,7 @@ public class FinRenderer { GLUtessellatorCallback cb = new GLUtessellatorCallbackAdapter() { @Override public void vertex(Object vertexData) { - double d[] = (double[]) vertexData; + double[] d = (double[]) vertexData; gl.glTexCoord2d(d[0], d[1]); gl.glVertex3dv(d, 0); } @@ -64,69 +64,82 @@ public class FinRenderer { public void end() { gl.glEnd(); } + + @Override + public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) { + double[] vertex = new double[3]; + vertex[0] = coords[0]; + vertex[1] = coords[1]; + vertex[2] = coords[2]; + outData[0] = vertex; + } }; - GLU.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, cb); - GLU.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, cb); - GLU.gluTessCallback(tobj, GLU.GLU_TESS_END, cb); - + GLU.gluTessCallback(tess, GLU.GLU_TESS_VERTEX, cb); + GLU.gluTessCallback(tess, GLU.GLU_TESS_BEGIN, cb); + GLU.gluTessCallback(tess, GLU.GLU_TESS_END, cb); + GLU.gluTessCallback(tess, GLU.GLU_TESS_COMBINE, cb); + // fin side: +z if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.INSIDE) { // Right side - GLU.gluTessBeginPolygon(tobj, null); - GLU.gluTessBeginContour(tobj); + GLU.gluTessBeginPolygon(tess, null); + GLU.gluTessBeginContour(tess); gl.glNormal3f(0, 0, 1); for (int i = finPoints.length - 1; i >= 0; i--) { Coordinate c = finPoints[i]; double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(), c.z + finSet.getThickness() / 2.0}; - GLU.gluTessVertex(tobj, p, 0, p); + GLU.gluTessVertex(tess, p, 0, p); } - GLU.gluTessEndContour(tobj); - GLU.gluTessEndPolygon(tobj); + GLU.gluTessEndContour(tess); + GLU.gluTessEndPolygon(tess); } // tab side: +z if (finSet.getTabHeight() > 0 && finSet.getTabLength() > 0 && which == Surface.INSIDE) { // Right side - GLU.gluTessBeginPolygon(tobj, null); - GLU.gluTessBeginContour(tobj); + GLU.gluTessBeginPolygon(tess, null); + GLU.gluTessBeginContour(tess); gl.glNormal3f(0, 0, 1); for (int i = tabPoints.length - 1; i >= 0; i--) { Coordinate c = tabPoints[i]; double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(), c.z + finSet.getThickness() / 2.0}; - GLU.gluTessVertex(tobj, p, 0, p); + GLU.gluTessVertex(tess, p, 0, p); } - GLU.gluTessEndContour(tobj); - GLU.gluTessEndPolygon(tobj); + GLU.gluTessEndContour(tess); + GLU.gluTessEndPolygon(tess); } // fin side: -z if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.OUTSIDE) { // Left side - GLU.gluTessBeginPolygon(tobj, null); - GLU.gluTessBeginContour(tobj); + GLU.gluTessBeginPolygon(tess, null); + GLU.gluTessBeginContour(tess); gl.glNormal3f(0, 0, -1); for (Coordinate c : finPoints) { double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(), c.z - finSet.getThickness() / 2.0}; - GLU.gluTessVertex(tobj, p, 0, p); + GLU.gluTessVertex(tess, p, 0, p); } - GLU.gluTessEndContour(tobj); - GLU.gluTessEndPolygon(tobj); + GLU.gluTessEndContour(tess); + GLU.gluTessEndPolygon(tess); } // tab side: -z if (finSet.getTabHeight() > 0 && finSet.getTabLength() > 0 && which == Surface.OUTSIDE) { // Left side - GLU.gluTessBeginPolygon(tobj, null); - GLU.gluTessBeginContour(tobj); + GLU.gluTessBeginPolygon(tess, null); + GLU.gluTessBeginContour(tess); gl.glNormal3f(0, 0, -1); for (Coordinate c : tabPoints) { double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(), c.z - finSet.getThickness() / 2.0}; - GLU.gluTessVertex(tobj, p, 0, p); + GLU.gluTessVertex(tess, p, 0, p); } - GLU.gluTessEndContour(tobj); - GLU.gluTessEndPolygon(tobj); + GLU.gluTessEndContour(tess); + GLU.gluTessEndPolygon(tess); } + + // delete tessellator after processing + GLU.gluDeleteTess(tess); // Fin strip around the edge if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.EDGES) { From 91332baafaf42eb34c71d7fe3ba925c1a9bb5118 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 12:43:24 +0100 Subject: [PATCH 18/22] [#1842] More general string conversion to double for fin editor table --- .../src/net/sf/openrocket/unit/UnitGroup.java | 5 +++-- .../net/sf/openrocket/util/StringUtil.java | 19 ++++++++++++++++ .../sf/openrocket/util/StringUtilTest.java | 22 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/core/src/net/sf/openrocket/unit/UnitGroup.java b/core/src/net/sf/openrocket/unit/UnitGroup.java index 796fc6d22..024fc3509 100644 --- a/core/src/net/sf/openrocket/unit/UnitGroup.java +++ b/core/src/net/sf/openrocket/unit/UnitGroup.java @@ -18,6 +18,7 @@ import java.util.regex.Pattern; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.util.StringUtil; /** @@ -670,8 +671,8 @@ public class UnitGroup { if (!matcher.matches()) { throw new NumberFormatException("string did not match required pattern"); } - - double value = Double.parseDouble(matcher.group(1)); + + double value = StringUtil.convertToDouble(matcher.group(1)); String unit = matcher.group(2).trim(); if (unit.equals("")) { diff --git a/core/src/net/sf/openrocket/util/StringUtil.java b/core/src/net/sf/openrocket/util/StringUtil.java index e5c3bb2ee..27be8ac44 100644 --- a/core/src/net/sf/openrocket/util/StringUtil.java +++ b/core/src/net/sf/openrocket/util/StringUtil.java @@ -19,5 +19,24 @@ public class StringUtil { } return "".equals(s.trim()); } + + /** + * Converts a string to a double, but with a more robust locale handling. + * Some systems use a comma as a decimal separator, some a dot. This method + * should work for both cases + * @param input string to convert + * @return double converted from string + * @throws NumberFormatException if the string cannot be parsed. + */ + public static double convertToDouble(String input) { + input = input.replace(',', '.'); + int decimalSeparator = input.lastIndexOf('.'); + + if (decimalSeparator > -1) { + input = input.substring(0, decimalSeparator).replace(".", "") + input.substring(decimalSeparator); + } + + return Double.parseDouble(input); + } } diff --git a/core/test/net/sf/openrocket/util/StringUtilTest.java b/core/test/net/sf/openrocket/util/StringUtilTest.java index 2d89772e5..7b1543d4c 100644 --- a/core/test/net/sf/openrocket/util/StringUtilTest.java +++ b/core/test/net/sf/openrocket/util/StringUtilTest.java @@ -4,6 +4,7 @@ import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; /** * A class that tests @@ -22,4 +23,25 @@ public class StringUtilTest { assertFalse(StringUtil.isEmpty("A")); assertFalse(StringUtil.isEmpty(" . ")); } + + @Test + public void testConvertToDouble() { + assertEquals(0.2, StringUtil.convertToDouble(".2"), MathUtil.EPSILON); + assertEquals(0.2, StringUtil.convertToDouble(",2"), MathUtil.EPSILON); + assertEquals(1, StringUtil.convertToDouble("1,"), MathUtil.EPSILON); + assertEquals(2, StringUtil.convertToDouble("2."), MathUtil.EPSILON); + assertEquals(1, StringUtil.convertToDouble("1"), MathUtil.EPSILON); + assertEquals(1.52, StringUtil.convertToDouble("1.52"), MathUtil.EPSILON); + assertEquals(1.52, StringUtil.convertToDouble("1,52"), MathUtil.EPSILON); + assertEquals(1.5, StringUtil.convertToDouble("1.500"), MathUtil.EPSILON); + assertEquals(1.5, StringUtil.convertToDouble("1,500"), MathUtil.EPSILON); + assertEquals(1500.61, StringUtil.convertToDouble("1.500,61"), MathUtil.EPSILON); + assertEquals(1500.61, StringUtil.convertToDouble("1,500.61"), MathUtil.EPSILON); + assertEquals(1500.2, StringUtil.convertToDouble("1,500,200"), MathUtil.EPSILON); + assertEquals(1500.2, StringUtil.convertToDouble("1.500.200"), MathUtil.EPSILON); + assertEquals(1500200.23, StringUtil.convertToDouble("1500200.23"), MathUtil.EPSILON); + assertEquals(1500200.23, StringUtil.convertToDouble("1500200,23"), MathUtil.EPSILON); + assertEquals(1500200.23, StringUtil.convertToDouble("1,500,200.23"), MathUtil.EPSILON); + assertEquals(1500200.23, StringUtil.convertToDouble("1.500.200,23"), MathUtil.EPSILON); + } } From 77822ec671eaa55c728221be44a9b1c1a7a0c383 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 16:02:35 +0100 Subject: [PATCH 19/22] [#1841] Wrap fin shape editor table and figure in split pane --- .../gui/configdialog/FreeformFinSetConfig.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index f3777b3b0..e6dc25f33 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -29,6 +29,7 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSpinner; +import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; @@ -303,10 +304,12 @@ public class FreeformFinSetConfig extends FinSetConfig { panel.setLayout(new MigLayout("fill, gap 5!","", "[nogrid, fill, sizegroup display, growprio 200]5![sizegroup text, growprio 5]5![sizegroup buttons, align top, growprio 5]0!")); // first row: main display - panel.add(tablePane, "width 100lp:100lp:, growy"); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tablePane, figurePane); + splitPane.setResizeWeight(0.1); + splitPane.setBorder(null); + panel.add(splitPane, "width 300lp:500lp:, gap unrel, grow, height 100lp:250lp:, wrap"); order.add(table); - panel.add(figurePane, "width 200lp:400lp:, gap unrel, grow, height 100lp:250lp:, wrap"); - + // row of text directly below figure panel.add(new StyledLabel(trans.get("lbl.doubleClick1")+" "+trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "spanx 3"); panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 3"); From 39100f76f6f2d00f80f6753948b5d89ca2fd26b9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 17:34:59 +0100 Subject: [PATCH 20/22] Remove redundant row query --- .../src/net/sf/openrocket/gui/main/SimulationPanel.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 04c657986..abc649ddd 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -208,13 +208,10 @@ public class SimulationPanel extends JPanel { } // Show context menu else if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) { - // Get the row that the right-click action happened on - int r = simulationTable.rowAtPoint(e.getPoint()); - // Select new row - if (!simulationTable.isRowSelected(r)) { - if (r >= 0 && r < simulationTable.getRowCount()) { - simulationTable.setRowSelectionInterval(r, r); + if (!simulationTable.isRowSelected(row)) { + if (row >= 0 && row < simulationTable.getRowCount()) { + simulationTable.setRowSelectionInterval(row, row); } else { return; } From 6f2cbf31ae3a2e8a71e84cf620a4e2b49b50d141 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 17:35:27 +0100 Subject: [PATCH 21/22] [#1840] Add context menu for fin point deletion --- core/resources/l10n/messages.properties | 1 + .../configdialog/FreeformFinSetConfig.java | 95 ++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index d1462bf8a..f2f28d500 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1030,6 +1030,7 @@ FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Delete point FreeformFinSetConfig.lbl.scaleFin = Scale Fin FreeformFinSetConfig.lbl.exportCSV = Export CSV +FreeformFinSetConfig.lbl.deletePoint = Delete point !TubeFinSetConfig TubeFinSetCfg.lbl.Nbroffins = Number of fins: diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index f3777b3b0..fd737b6e3 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.EventObject; import java.util.List; +import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; @@ -26,6 +27,7 @@ import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSpinner; @@ -33,9 +35,12 @@ import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import net.sf.openrocket.gui.adaptors.CustomFocusTraversalPolicy; +import net.sf.openrocket.gui.util.Icons; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +82,7 @@ public class FreeformFinSetConfig extends FinSetConfig { private JTable table = null; private FinPointTableModel tableModel = null; + private JPopupMenu pm; private int dragIndex = -1; private Point dragPoint = null; @@ -84,6 +90,8 @@ public class FreeformFinSetConfig extends FinSetConfig { private FinPointFigure figure = null; private ScaleScrollPane figurePane = null; private ScaleSelector selector; + + private FinPointAction deleteFinPointAction; public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component, JDialog parent) { super(d, component, parent); @@ -246,11 +254,31 @@ public class FreeformFinSetConfig extends FinSetConfig { } table.addMouseListener(new MouseAdapter() { @Override - public void mouseClicked(MouseEvent ev) { + public void mouseClicked(MouseEvent e) { + int row = table.rowAtPoint(e.getPoint()); + + // Context menu on right-click + if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) { + // Select new row + if (!table.isRowSelected(row)) { + if (row >= 0 && row < table.getRowCount()) { + table.setRowSelectionInterval(row, row); + } else { + return; + } + } + + doPopup(e); + } + } + + }); + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { figure.setSelectedIndex(table.getSelectedRow()); figure.updateFigure(); } - }); JScrollPane tablePane = new JScrollPane(table); @@ -264,6 +292,19 @@ public class FreeformFinSetConfig extends FinSetConfig { dialog.dispose(); } }); + + // Context menu for table + deleteFinPointAction = new DeletePointAction(); + pm = new JPopupMenu(); + pm.add(deleteFinPointAction); + + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + updateActionStates(); + } + }); + // panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%"); // panel.add(new JLabel(" View:"), "wrap, aligny bottom"); @@ -414,6 +455,32 @@ public class FreeformFinSetConfig extends FinSetConfig { figurePane.revalidate(); } } + + /** + * Delete the selected point in the fin point table. + */ + private void deletePoint() { + int row = table.getSelectedRow(); + if (row == -1) { + return; + } + final FreeformFinSet finSet = (FreeformFinSet) component; + try { + finSet.removePoint(row); + } catch (IllegalFinPointException ex) { + throw new RuntimeException(ex); + } + } + + private void doPopup(MouseEvent e) { + pm.show(e.getComponent(), e.getX(), e.getY()); + } + + private void updateActionStates() { + if (deleteFinPointAction != null) { + deleteFinPointAction.updateEnabledState(); + } + } private class FinPointScrollPane extends ScaleScrollPane { @@ -693,4 +760,28 @@ public class FreeformFinSetConfig extends FinSetConfig { } } } + + private abstract static class FinPointAction extends AbstractAction { + private static final long serialVersionUID = 1L; + + public abstract void updateEnabledState(); + } + + private class DeletePointAction extends FinPointAction { + public DeletePointAction() { + putValue(NAME, trans.get("FreeformFinSetConfig.lbl.deletePoint")); + this.putValue(SMALL_ICON, Icons.EDIT_DELETE); + } + + @Override + public void actionPerformed(ActionEvent e) { + deletePoint(); + } + + @Override + public void updateEnabledState() { + // You can't delete the first or last fin point + setEnabled(table.getSelectedRow() > 0 && table.getSelectedRow() < table.getRowCount() - 1); + } + } } From 6bd2ac05aa6c13ffe73ba38cf7a7a1816bc61ddd Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 17 Nov 2022 19:17:09 +0100 Subject: [PATCH 22/22] Add insert fin point context menu action --- core/resources/l10n/messages.properties | 1 + .../configdialog/FreeformFinSetConfig.java | 52 ++++++++++++++++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index f2f28d500..3609b0d06 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1031,6 +1031,7 @@ FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Delete point FreeformFinSetConfig.lbl.scaleFin = Scale Fin FreeformFinSetConfig.lbl.exportCSV = Export CSV FreeformFinSetConfig.lbl.deletePoint = Delete point +FreeformFinSetConfig.lbl.insertPoint = Insert point !TubeFinSetConfig TubeFinSetCfg.lbl.Nbroffins = Number of fins: diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index fd737b6e3..13a6c7a57 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -91,6 +91,7 @@ public class FreeformFinSetConfig extends FinSetConfig { private ScaleScrollPane figurePane = null; private ScaleSelector selector; + private FinPointAction insertFinPointAction; private FinPointAction deleteFinPointAction; public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component, JDialog parent) { @@ -294,8 +295,10 @@ public class FreeformFinSetConfig extends FinSetConfig { }); // Context menu for table + insertFinPointAction = new InsertPointAction(); deleteFinPointAction = new DeletePointAction(); pm = new JPopupMenu(); + pm.add(insertFinPointAction); pm.add(deleteFinPointAction); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @@ -457,16 +460,32 @@ public class FreeformFinSetConfig extends FinSetConfig { } /** - * Delete the selected point in the fin point table. + * Insert a new fin point between the currently selected point and the next point. + * The coordinates of the new point will be the average of the two points. + */ + private void insertPoint() { + int currentPointIdx = table.getSelectedRow(); + if (currentPointIdx == -1 || currentPointIdx >= table.getRowCount() - 1) { + return; + } + final FreeformFinSet finSet = (FreeformFinSet) component; + Coordinate currentPoint = finSet.getFinPoints()[currentPointIdx]; + Coordinate nextPoint = finSet.getFinPoints()[currentPointIdx + 1]; + Point2D.Double toAdd = new Point2D.Double((currentPoint.x + nextPoint.x) / 2, (currentPoint.y + nextPoint.y) / 2); + finSet.addPoint(currentPointIdx + 1, toAdd); + } + + /** + * Delete the currently selected fin point. */ private void deletePoint() { - int row = table.getSelectedRow(); - if (row == -1) { + int currentPointIdx = table.getSelectedRow(); + if (currentPointIdx == -1) { return; } final FreeformFinSet finSet = (FreeformFinSet) component; try { - finSet.removePoint(row); + finSet.removePoint(currentPointIdx); } catch (IllegalFinPointException ex) { throw new RuntimeException(ex); } @@ -477,9 +496,12 @@ public class FreeformFinSetConfig extends FinSetConfig { } private void updateActionStates() { - if (deleteFinPointAction != null) { - deleteFinPointAction.updateEnabledState(); + if (insertFinPointAction == null) { // If one of the actions is null, the rest will be too + return; } + + insertFinPointAction.updateEnabledState(); + deleteFinPointAction.updateEnabledState(); } private class FinPointScrollPane extends ScaleScrollPane { @@ -767,6 +789,24 @@ public class FreeformFinSetConfig extends FinSetConfig { public abstract void updateEnabledState(); } + private class InsertPointAction extends FinPointAction { + public InsertPointAction() { + putValue(NAME, trans.get("FreeformFinSetConfig.lbl.insertPoint")); + this.putValue(SMALL_ICON, Icons.FILE_NEW); + } + + @Override + public void actionPerformed(ActionEvent e) { + insertPoint(); + } + + @Override + public void updateEnabledState() { + // You can't add to the last fin point + setEnabled(table.getSelectedRow() < table.getRowCount() - 1); + } + } + private class DeletePointAction extends FinPointAction { public DeletePointAction() { putValue(NAME, trans.get("FreeformFinSetConfig.lbl.deletePoint"));