Merge branch 'unstable' into issue-1818

This commit is contained in:
Sibo Van Gool 2022-11-18 19:13:55 +01:00 committed by GitHub
commit 29020856cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 505 additions and 139 deletions

View File

@ -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 = <html>Could not instantiate class %s.<br>Does it have a zero-argument, or @Inject constructor?</html>
SimulationExtension.scripting.name = {language} script
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
@ -552,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.
@ -1028,6 +1031,8 @@ 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
FreeformFinSetConfig.lbl.insertPoint = Insert point
!TubeFinSetConfig
TubeFinSetCfg.lbl.Nbroffins = Number of fins:
@ -1827,7 +1832,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
@ -1844,6 +1849,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -67,6 +67,9 @@ 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);
/**
* Test component assembly for continuity (esp. diameter), and post any needed warnings
*/
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent component, WarningSet warnings);
}

View File

@ -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();
@ -276,13 +274,9 @@ 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 ){
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){
Queue<RocketComponent> queue = new LinkedList<>();
for (RocketComponent child : treeRoot.getChildren()) {
// Ignore inactive stages
@ -292,9 +286,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 +306,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 +320,10 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
prevComp = sym;
}else if( comp instanceof ComponentAssembly ){
isContinuous &= testIsContinuous(configuration, comp);
testIsContinuous(configuration, comp, warnings);
}
}
return isContinuous;
}

View File

@ -18,7 +18,6 @@ public abstract class Warning {
return new Warning.Other(text);
}
/**
* Return <code>true</code> if the <code>other</code> warning should replace
* this warning. The method should return <code>true</code> 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"));
}

View File

@ -67,7 +67,17 @@ public class WarningSet extends AbstractSet<Warning> implements Cloneable, Monit
mutable.check();
return add(Warning.fromString(s));
}
/**
* Add a <code>Warning</code> 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<Warning> iterator() {

View File

@ -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);
}
}

View File

@ -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() {
}

View File

@ -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,11 +70,25 @@ 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 (!simulationConfig.hasRecoveryDevice()) {
currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE);
}
currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
{
// main simulation branch
@ -97,6 +113,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);
@ -108,7 +130,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())
@ -240,9 +262,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();
@ -341,11 +365,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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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("")) {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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,16 +27,21 @@ 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;
import javax.swing.JSplitPane;
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 +83,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 +91,9 @@ public class FreeformFinSetConfig extends FinSetConfig {
private FinPointFigure figure = null;
private ScaleScrollPane figurePane = null;
private ScaleSelector selector;
private FinPointAction insertFinPointAction;
private FinPointAction deleteFinPointAction;
public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component, JDialog parent) {
super(d, component, parent);
@ -246,11 +256,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 +294,21 @@ public class FreeformFinSetConfig extends FinSetConfig {
dialog.dispose();
}
});
// Context menu for table
insertFinPointAction = new InsertPointAction();
deleteFinPointAction = new DeletePointAction();
pm = new JPopupMenu();
pm.add(insertFinPointAction);
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");
@ -303,10 +348,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");
@ -414,6 +461,51 @@ public class FreeformFinSetConfig extends FinSetConfig {
figurePane.revalidate();
}
}
/**
* 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 currentPointIdx = table.getSelectedRow();
if (currentPointIdx == -1) {
return;
}
final FreeformFinSet finSet = (FreeformFinSet) component;
try {
finSet.removePoint(currentPointIdx);
} catch (IllegalFinPointException ex) {
throw new RuntimeException(ex);
}
}
private void doPopup(MouseEvent e) {
pm.show(e.getComponent(), e.getX(), e.getY());
}
private void updateActionStates() {
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 {
@ -693,4 +785,46 @@ public class FreeformFinSetConfig extends FinSetConfig {
}
}
}
private abstract static class FinPointAction extends AbstractAction {
private static final long serialVersionUID = 1L;
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"));
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);
}
}
}

View File

@ -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) {

View File

@ -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;
@ -259,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() );
}
}

View File

@ -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<RocketComponent> 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<RocketComponent> 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<RocketComponent> 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<RocketComponent> 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.
*/

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}
});

View File

@ -237,6 +237,7 @@ class SimulationOptionsPanel extends JPanel {
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
if (configurator != null) {
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
updateCurrentExtensions();
}
}
});
@ -281,6 +282,7 @@ class SimulationOptionsPanel extends JPanel {
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
if (configurator != null) {
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
updateCurrentExtensions();
}
}
});

View File

@ -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;
@ -207,9 +209,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 +262,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().
@ -273,6 +290,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 +305,8 @@ public class SimulationRunDialog extends JDialog {
public InteractiveSimulationWorker(OpenRocketDocument doc, Simulation sim, int index) {
super(sim);
List<CustomExpression> exprs = doc.getCustomExpressions();
this.document = doc;
List<CustomExpression> exprs = document.getCustomExpressions();
exprListener = new CustomExpressionSimulationListener(exprs);
this.index = index;
@ -389,6 +408,7 @@ public class SimulationRunDialog extends JDialog {
log.debug("Simulation done");
setSimulationProgress(1.0);
updateProgress();
document.fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
}
/**

View File

@ -70,6 +70,17 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
protected SimulationListener[] getExtraListeners() {
return new SimulationListener[0];
}
/**
* Returns the throwable that caused the simulation to fail or cancel,
* or null if the simulation ran successfully.
*
* @return throwable that caused the simulation to fail or cancel,
* or null if the simulation ran successfully.
*/
public Throwable getThrowable() {
return throwable;
}
/**

View File

@ -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<E extends Sim
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
close();
}
});
panel.add(close, "right");
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
close();
}
});
dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setVisible(true);
close();
GUIUtil.setNullModels(dialog);
dialog = null;
}
/**
@ -78,10 +84,12 @@ public abstract class AbstractSwingSimulationExtensionConfigurator<E extends Sim
}
/**
* Called when the default dialog is closed. By default does nothing.
* Called when the default dialog is closed. By default hides the dialog and cleans up the component models.
*/
protected void close() {
dialog.setVisible(false);
GUIUtil.setNullModels(dialog);
dialog = null;
}
protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel);

View File

@ -8,22 +8,38 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Color;
@Plugin
public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfigurator<JavaCode> {
private JavaCode extension;
private JTextField classNameField;
private StyledLabel errorMsg;
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");
this.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 +53,41 @@ public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfig
}
public void update() {
extension.setClassName(textField.getText());
updateErrorMsg();
}
});
panel.add(textField, "growx");
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) {
this.extension.setClassName(this.classNameField.getText().trim());
}
super.close();
}
}