diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index 0c13b37f3..fc47de6a3 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.
@@ -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
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..9889a4987 100644
--- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java
+++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java
@@ -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);
}
diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
index b85074405..ac75e95a4 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();
@@ -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 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;
}
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/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);
}
}
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/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
index 339c0704b..63e3f1d7b 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,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);
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);
}
}
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;
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/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
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);
+ }
}
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
index f3777b3b0..66ba5710c 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,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);
+ }
+ }
}
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) {
diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
index de604e585..8134f1fb7 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;
@@ -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() );
}
}
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.
*/
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;
}
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;
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;
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/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
index b0ca3a4cc..8ff5dd1f1 100644
--- a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
+++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
@@ -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();
}
}
});
diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java
index 44416bf24..63ac79c29 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;
@@ -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 exprs = doc.getCustomExpressions();
+ this.document = doc;
+ List 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));
}
/**
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 {
-
+ 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();
+ }
}