diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index d2dda908b..0cd7e30d5 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -537,7 +537,8 @@ simpanel.pop.plot = Plot / Export
simpanel.pop.run = Run
simpanel.pop.delete = Delete
simpanel.pop.duplicate = Duplicate
-simpanel.pop.exportToCSV = Export table as CSV file
+simpanel.pop.exportSimTableToCSV = Export simulation table as CSV file
+simpanel.pop.exportSelectedSimsToCSV = Export simulations as CSV file
simpanel.pop.exportToCSV.save.dialog.title = Save as CSV file
simpanel.dlg.no.simulation.table.rows = Simulation table has no entries\u2026 Please run a simulation first.
simpanel.checkbox.donotask = Do not ask me again
@@ -575,9 +576,11 @@ SimuRunDlg.lbl.Altitude = Altitude:
SimuRunDlg.lbl.Velocity = Velocity:
SimuRunDlg.msg.Unabletosim = Unable to simulate:
SimuRunDlg.msg.errorOccurred = An error occurred during the simulation:
+SimuRunDlg.msg.branchErrorOccurred = An error occurred during simulation branch
BasicEventSimulationEngine.error.noMotorsDefined = No motors defined in the simulation.
-BasicEventSimulationEngine.error.cantCalculateStability = Can't calculate rocket stability.
+BasicEventSimulationEngine.error.activeLengthZero = Active airframe has length 0
+BasicEventSimulationEngine.error.cantCalculateStability = Can't calculate stability
BasicEventSimulationEngine.error.earlyMotorBurnout = Motor burnout without liftoff.
BasicEventSimulationEngine.error.noConfiguredIgnition = No motors configured to ignite at liftoff
BasicEventSimulationEngine.error.noIgnition = No motors ignited.
@@ -1364,6 +1367,7 @@ TCMotorSelPan.lbl.motorNameColumn.ttip = Select which motor property to display
TCMotorSelPan.btn.commonName = Use common name
TCMotorSelPan.btn.designation = Use manufacturer's designation
TCMotorSelPan.lbl.nrOfMotors = Number of motors:
+TCMotorSelPan.lbl.nrOfMotors.None = None
TCMotorSelPan.lbl.ttip.nrOfMotors = Number of motors currently visible in the motor selection table
TCMotorSelPan.checkbox.limitlength = Limit motor length to mount length
TCMotorSelPan.checkbox.limitdiameter = Limit motor diameter to mount diameter
@@ -1377,8 +1381,7 @@ TCMotorSelPan.MotorMountDimensions = Motor mount dimensions:
TCMotorSelPan.lbl.Search = Search:
TCMotorSelPan.lbl.Selectthrustcurve = Select thrust curve:
TCMotorSelPan.lbl.Ejectionchargedelay = Ejection charge delay:
-TCMotorSelPan.equalsIgnoreCase.None = None
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Number of seconds or \"None\")
+TCMotorSelPan.lbl.Numberofseconds = (or type in desired delay in seconds)
TCMotorSelPan.lbl.Designation = Designation:
TCMotorSelPan.lbl.CommonName = Common name:
TCMotorSelPan.lbl.Totalimpulse = Total impulse:
@@ -1395,7 +1398,8 @@ TCMotorSelPan.lbl.Datapoints = Data points:
TCMotorSelPan.lbl.Digest = Digest:
TCMotorSelPan.title.Thrustcurve = Thrust curve:
TCMotorSelPan.title.Thrust = Thrust
-TCMotorSelPan.delayBox.None = Plugged (None)
+TCMotorSelPan.delayBox.Plugged = Plugged
+TCMotorSelPan.delayBox.PluggedNone = None
TCMotorSelPan.noDescription = No description available.
TCMotorSelPan.btn.checkAll = Select All
TCMotorSelPan.btn.checkNone = Clear All
@@ -1896,6 +1900,7 @@ Warning.PODSET_FORWARD = In-line podset forward of parent airframe component
Warning.PODSET_OVERLAP = In-line podset overlaps parent airframe component
Warning.THICK_FIN = Thick fins may not simulate accurately.
Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate.
+Warning.ZERO_AREA_FIN = Fins with zero area will not affect aerodynamics
Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation
Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING = Recovery device opened while motor still burning.
Warning.FILE_INVALID_PARAMETER = Invalid parameter encountered, ignoring.
diff --git a/core/resources/l10n/messages_ar.properties b/core/resources/l10n/messages_ar.properties
index bd36758b4..5db732751 100644
--- a/core/resources/l10n/messages_ar.properties
+++ b/core/resources/l10n/messages_ar.properties
@@ -1335,8 +1335,7 @@ TCMotorSelPan.MotorMountDimensions = :أبعاد حامل المحرك
TCMotorSelPan.lbl.Search = :بحث
TCMotorSelPan.lbl.Selectthrustcurve = :حدد منحنى الدفع
TCMotorSelPan.lbl.Ejectionchargedelay = :تأخير شحنة الإطلاق
-TCMotorSelPan.equalsIgnoreCase.None = لا شئ
-TCMotorSelPan.lbl.NumberofsecondsorNone = (\"لا شئ\" عدد الثواني أو)
+TCMotorSelPan.lbl.Numberofseconds = (\"لا شئ\" عدد الثواني أو)
TCMotorSelPan.lbl.Designation = :تسمية الشركة المصنعة
TCMotorSelPan.lbl.Totalimpulse = :الدفع الكلي
TCMotorSelPan.lbl.Avgthrust = :متوسط الدفع
@@ -1351,7 +1350,7 @@ TCMotorSelPan.lbl.Datapoints = :نقاط البيانات
TCMotorSelPan.lbl.Digest = :الإستيعاب
TCMotorSelPan.title.Thrustcurve = :منحنى الدفع
TCMotorSelPan.title.Thrust = الدفع
-TCMotorSelPan.delayBox.None = لا شئ
+TCMotorSelPan.delayBox.Plugged = لا شئ
TCMotorSelPan.noDescription = .لا يوجد وصف متاح
TCMotorSelPan.btn.checkAll = تحديد الكل
TCMotorSelPan.btn.checkNone = مسح الكل
diff --git a/core/resources/l10n/messages_cs.properties b/core/resources/l10n/messages_cs.properties
index 3f747027a..08a09dffd 100644
--- a/core/resources/l10n/messages_cs.properties
+++ b/core/resources/l10n/messages_cs.properties
@@ -940,8 +940,7 @@ TCMotorSelPan.lbl.Motormountdia = Prumer uchycen
TCMotorSelPan.lbl.Search = Hledej:
TCMotorSelPan.lbl.Selectthrustcurve = Vyber vkonovou krivku:
TCMotorSelPan.lbl.Ejectionchargedelay = Oddelovac zpo\u017Eden:
-TCMotorSelPan.equalsIgnoreCase.None = Nic
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Pocet sekund nebo \"Nic\")
+TCMotorSelPan.lbl.Numberofseconds = (Pocet sekund nebo \"Nic\")
TCMotorSelPan.lbl.Designation = Pojmenovn:
TCMotorSelPan.lbl.Totalimpulse = Celkov impulse:
TCMotorSelPan.lbl.Avgthrust = Prumern tah:
@@ -953,7 +952,7 @@ TCMotorSelPan.lbl.Datapoints = Datov
TCMotorSelPan.lbl.Digest = Vber:
TCMotorSelPan.title.Thrustcurve = Vkonov krivka:
TCMotorSelPan.title.Thrust = Tah
-TCMotorSelPan.delayBox.None = Nic
+TCMotorSelPan.delayBox.Plugged = Nic
! PlotDialog
diff --git a/core/resources/l10n/messages_de.properties b/core/resources/l10n/messages_de.properties
index f1624bde5..0c68ebee2 100644
--- a/core/resources/l10n/messages_de.properties
+++ b/core/resources/l10n/messages_de.properties
@@ -998,8 +998,7 @@ TCMotorSelPan.lbl.Motormountdia = Durchmesser der Motorhalterung
TCMotorSelPan.lbl.Search = Suchen:
TCMotorSelPan.lbl.Selectthrustcurve = Schubkurve auswhlen:
TCMotorSelPan.lbl.Ejectionchargedelay = Verzgerung der Ausstoladung:
-TCMotorSelPan.equalsIgnoreCase.None = keine
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Anzahl der Sekunden oder \keine\)
+TCMotorSelPan.lbl.Numberofseconds = (Anzahl der Sekunden oder \keine\)
TCMotorSelPan.lbl.Designation = Bezeichnung:
TCMotorSelPan.lbl.Totalimpulse = Gesamtimpuls:
TCMotorSelPan.lbl.Avgthrust = Durchschn. Schub:
@@ -1011,7 +1010,7 @@ TCMotorSelPan.lbl.Datapoints = Datenpunkte:
TCMotorSelPan.lbl.Digest = bersicht:
TCMotorSelPan.title.Thrustcurve = Schubkurve:
TCMotorSelPan.title.Thrust = Schub:
-TCMotorSelPan.delayBox.None = keine
+TCMotorSelPan.delayBox.Plugged = keine
! PlotDialog
diff --git a/core/resources/l10n/messages_es.properties b/core/resources/l10n/messages_es.properties
index 9ce7011f4..3d989be9b 100644
--- a/core/resources/l10n/messages_es.properties
+++ b/core/resources/l10n/messages_es.properties
@@ -1125,8 +1125,7 @@ TCMotorSelPan.checkbox.hideSimilar = Borrar las curvas similares
TCMotorSelPan.checkbox.hideUsed = Ocultar los motores que ya est\u00e1n instalados
TCMotorSelPan.checkbox.limitdiameter = Limitar el di\u00e1metro de motor al di\u00e1metro del porta motor
TCMotorSelPan.checkbox.limitlength = Limitar la longitud del motor a la longitud del porta motor
-TCMotorSelPan.delayBox.None = Ninguno
-TCMotorSelPan.equalsIgnoreCase.None = Ninguno
+TCMotorSelPan.delayBox.Plugged = Ninguno
TCMotorSelPan.lbl.Avgthrust = Empuje medio:
TCMotorSelPan.lbl.Burntime = Tiempo de quemado:
TCMotorSelPan.lbl.Datapoints = Muestras de la curva:
@@ -1136,7 +1135,7 @@ TCMotorSelPan.lbl.Emptymass = Masa carcasa:
TCMotorSelPan.lbl.Launchmass = Masa total:
TCMotorSelPan.lbl.Maxthrust = Empuje m\u00e1ximo:
TCMotorSelPan.lbl.Motormountdia = Di\u00e1metro del porta motor:
-TCMotorSelPan.lbl.NumberofsecondsorNone = (segundos)
+TCMotorSelPan.lbl.Numberofseconds = (segundos)
TCMotorSelPan.lbl.Search = Buscar:
TCMotorSelPan.lbl.Selectthrustcurve = Seleccione curva de empuje:
TCMotorSelPan.lbl.Selrocketmotor = Seleccione el motor del cohete:
diff --git a/core/resources/l10n/messages_fr.properties b/core/resources/l10n/messages_fr.properties
index ebcc8276c..0eff3be76 100644
--- a/core/resources/l10n/messages_fr.properties
+++ b/core/resources/l10n/messages_fr.properties
@@ -1119,8 +1119,7 @@ TCMotorSelPan.checkbox.hideSimilar = Cacher les courbes de pouss\u00E9es si
TCMotorSelPan.checkbox.hideUsed = Ne pas afficher les moteurs d\u00E9j\u00E0 utilis\u00E9s
TCMotorSelPan.checkbox.limitdiameter = Afficher uniquement les moteurs dont le diametre est \u00E9gale au porte moteur
TCMotorSelPan.checkbox.limitlength = Limiter la longueur du moteur \u00E0 celle du porte moteur
-TCMotorSelPan.delayBox.None = Aucun
-TCMotorSelPan.equalsIgnoreCase.None = Aucun
+TCMotorSelPan.delayBox.Plugged = Aucun
TCMotorSelPan.lbl.Avgthrust = Pouss\u00E9e moyenne:
TCMotorSelPan.lbl.Burntime = Temps de combustion:
TCMotorSelPan.lbl.Datapoints = Points de donn\u00E9es:
@@ -1130,7 +1129,7 @@ TCMotorSelPan.lbl.Emptymass = Masse \u00E0 vide:
TCMotorSelPan.lbl.Launchmass = Masse au lancement:
TCMotorSelPan.lbl.Maxthrust = Pouss\u00E9e Max.:
TCMotorSelPan.lbl.Motormountdia = Diam\u00E8tre du tube porte moteur:
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Nombre de secondes ou "Aucun")
+TCMotorSelPan.lbl.Numberofseconds = (Nombre de secondes ou "Aucun")
TCMotorSelPan.lbl.Search = Rechercher:
TCMotorSelPan.lbl.Selectthrustcurve = Choisir la courbe de pouss\u00E9e:
TCMotorSelPan.lbl.Selrocketmotor = Choisir le moteur fus\u00E9e:
diff --git a/core/resources/l10n/messages_it.properties b/core/resources/l10n/messages_it.properties
index 5b153d660..55c33cc87 100644
--- a/core/resources/l10n/messages_it.properties
+++ b/core/resources/l10n/messages_it.properties
@@ -999,8 +999,7 @@ TCMotorSelPan.lbl.Motormountdia = Diametro dell'alloggio del motore:
TCMotorSelPan.lbl.Search = Cerca:
TCMotorSelPan.lbl.Selectthrustcurve = Seleziona la curva di spinta:
TCMotorSelPan.lbl.Ejectionchargedelay = Ritardo della carica di espulsione:
-TCMotorSelPan.equalsIgnoreCase.None = Nessun
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Numero di secondi o \"Nessuno\")
+TCMotorSelPan.lbl.Numberofseconds = (Numero di secondi o \"Nessuno\")
TCMotorSelPan.lbl.Designation = Classe:
TCMotorSelPan.lbl.Totalimpulse = Impulso totale:
TCMotorSelPan.lbl.Avgthrust = Spinta media:
@@ -1012,7 +1011,7 @@ TCMotorSelPan.lbl.Datapoints = Punti:
TCMotorSelPan.lbl.Digest = Riassunto:
TCMotorSelPan.title.Thrustcurve = Curva di spinta:
TCMotorSelPan.title.Thrust = Spinta
-TCMotorSelPan.delayBox.None = Nessuno
+TCMotorSelPan.delayBox.Plugged = Nessuno
! PlotDialog
diff --git a/core/resources/l10n/messages_ja.properties b/core/resources/l10n/messages_ja.properties
index 79f13487b..947c2022a 100644
--- a/core/resources/l10n/messages_ja.properties
+++ b/core/resources/l10n/messages_ja.properties
@@ -1029,8 +1029,7 @@ TCMotorSelPan.lbl.Motormountdia = \u30E2\u30FC\u30BF\u30FC\u30DE\u30A6\u30F3\u3
TCMotorSelPan.lbl.Search = \u691C\u7D22\uFF1A
TCMotorSelPan.lbl.Selectthrustcurve = \u63A8\u529B\u5C65\u6B74\uFF1A
TCMotorSelPan.lbl.Ejectionchargedelay = Ejection charge delay:
-TCMotorSelPan.equalsIgnoreCase.None = None
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Number of seconds or \"None\")
+TCMotorSelPan.lbl.Numberofseconds = (Number of seconds or \"None\")
TCMotorSelPan.lbl.Designation = \u8A18\u53F7
TCMotorSelPan.lbl.Totalimpulse = \u30C8\u30FC\u30BF\u30EB\u30A4\u30F3\u30D1\u30EB\u30B9\uFF1A
TCMotorSelPan.lbl.Avgthrust = \u5E73\u5747\u63A8\u529B\uFF1A
@@ -1042,7 +1041,7 @@ TCMotorSelPan.lbl.Datapoints = \u30C7\u30FC\u30BF\u70B9\uFF1A
TCMotorSelPan.lbl.Digest = \u30C0\u30A4\u30B8\u30A7\u30B9\u30C8\uFF1A
TCMotorSelPan.title.Thrustcurve = \u63A8\u529B\u5C65\u6B74\uFF1A
TCMotorSelPan.title.Thrust = \u63A8\u529B
-TCMotorSelPan.delayBox.None = Plugged (None)
+TCMotorSelPan.delayBox.Plugged = Plugged (None)
TCMotorSelPan.noDescription = No description available.
diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties
index 0b152729d..a2de9a92c 100644
--- a/core/resources/l10n/messages_nl.properties
+++ b/core/resources/l10n/messages_nl.properties
@@ -1260,8 +1260,7 @@ TCMotorSelPan.MotorMountDimensions = Motorbevestiging afmetingen:
TCMotorSelPan.lbl.Search = Zoek:
TCMotorSelPan.lbl.Selectthrustcurve = Selecteer stuwkrachtcurve:
TCMotorSelPan.lbl.Ejectionchargedelay = Vertraging schietlading:
-TCMotorSelPan.equalsIgnoreCase.None = Geen
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Aantal seconden of \"Geen\")
+TCMotorSelPan.lbl.Numberofseconds = (Aantal seconden of \"Geen\")
TCMotorSelPan.lbl.Designation = Benaming
TCMotorSelPan.lbl.Totalimpulse = Totale impuls:
TCMotorSelPan.lbl.Avgthrust = Gemiddelde stuwkracht:
@@ -1276,7 +1275,7 @@ TCMotorSelPan.lbl.Datapoints = Datapunten:
TCMotorSelPan.lbl.Digest = Verteer:
TCMotorSelPan.title.Thrustcurve = Stuwkrachtcurve:
TCMotorSelPan.title.Thrust = Stuwkracht
-TCMotorSelPan.delayBox.None = Geen
+TCMotorSelPan.delayBox.Plugged = Geen
TCMotorSelPan.noDescription = Geen beschrijving beschikbaar.
TCMotorSelPan.btn.checkAll = Selecteer Alles
TCMotorSelPan.btn.checkNone = Alles wissen
diff --git a/core/resources/l10n/messages_pl.properties b/core/resources/l10n/messages_pl.properties
index 9673f47e7..55577b105 100644
--- a/core/resources/l10n/messages_pl.properties
+++ b/core/resources/l10n/messages_pl.properties
@@ -943,8 +943,7 @@ ComponentInfo.EngineBlock = Blokada silnika unieruchamia silnik wewn\u01
TCMotorSelPan.lbl.Search = Szukaj:
TCMotorSelPan.lbl.Selectthrustcurve = Wybierz krzyw\u0105 si\u0142y ci\u0105gu:
TCMotorSelPan.lbl.Ejectionchargedelay = Op\u017Anienie odpalenia \u0142adunku odrzucaj\u0105cego:
- TCMotorSelPan.equalsIgnoreCase.None = \u017Badne
- TCMotorSelPan.lbl.NumberofsecondsorNone = (liczba sekund lub \"Brak\")
+ TCMotorSelPan.lbl.Numberofseconds = (liczba sekund lub \"Brak\")
TCMotorSelPan.lbl.Designation = Oznaczenie:
TCMotorSelPan.lbl.Totalimpulse = Ca\u0142kowity impuls:
TCMotorSelPan.lbl.Avgthrust = \u015Arednia si\u0142a ci\u0105gu:
@@ -956,7 +955,7 @@ ComponentInfo.EngineBlock = Blokada silnika unieruchamia silnik wewn\u01
TCMotorSelPan.lbl.Digest = Podsumowanie:
TCMotorSelPan.title.Thrustcurve = Krzywa si\u0142y ci\u0105gu:
TCMotorSelPan.title.Thrust = Ci\u0105g
- TCMotorSelPan.delayBox.None = \u017Badne
+ TCMotorSelPan.delayBox.Plugged = \u017Badne
! PlotDialog
diff --git a/core/resources/l10n/messages_pt.properties b/core/resources/l10n/messages_pt.properties
index f7a86da1c..b9e7669c5 100644
--- a/core/resources/l10n/messages_pt.properties
+++ b/core/resources/l10n/messages_pt.properties
@@ -1083,8 +1083,7 @@ TCMotorSelPan.SHOW_DESCRIPTIONS.desc1 = Mostrar todos os motores
TCMotorSelPan.SHOW_DESCRIPTIONS.desc2 = Mostrar motores com um di\u00e2metro menor do que a montagem do motor
TCMotorSelPan.SHOW_DESCRIPTIONS.desc3 = Mostrar motores com um di\u00e2metro igual ao da montagem do motor
TCMotorSelPan.checkbox.hideSimilar = Esconder as curvas de impulso muito semelhantes
-TCMotorSelPan.delayBox.None = Nenhum
-TCMotorSelPan.equalsIgnoreCase.None = Nenhum
+TCMotorSelPan.delayBox.Plugged = Nenhum
TCMotorSelPan.lbl.Avgthrust = Empuxo m\u00e9dio:
TCMotorSelPan.lbl.Burntime = Tempo de queima:
TCMotorSelPan.lbl.Datapoints = Pontos de dados:
@@ -1094,7 +1093,7 @@ TCMotorSelPan.lbl.Emptymass = Massa em vazio:
TCMotorSelPan.lbl.Launchmass = Massa do lan\u00e7amento:
TCMotorSelPan.lbl.Maxthrust = Empuxo m\u00e1ximo:
TCMotorSelPan.lbl.Motormountdia = Di\u00e2metro da montagem do motor:
-TCMotorSelPan.lbl.NumberofsecondsorNone = (N\u00famero de segundos ou "Nenhum")
+TCMotorSelPan.lbl.Numberofseconds = (N\u00famero de segundos ou "Nenhum")
TCMotorSelPan.lbl.Search = Pesquisar:
TCMotorSelPan.lbl.Selectthrustcurve = Selecione curva de empuxo:
TCMotorSelPan.lbl.Selrocketmotor = Selecione motor do foguete:
diff --git a/core/resources/l10n/messages_ru.properties b/core/resources/l10n/messages_ru.properties
index 233e84419..4cc9d0b6b 100644
--- a/core/resources/l10n/messages_ru.properties
+++ b/core/resources/l10n/messages_ru.properties
@@ -1307,8 +1307,7 @@ TCMotorSelPan.MotorMountDimensions = \u0420\u0430\u0437\u043C\u0435\u0440\u044B
TCMotorSelPan.lbl.Search = \u041F\u043E\u0438\u0441\u043A:
TCMotorSelPan.lbl.Selectthrustcurve = \u0412\u044B\u0431\u043E\u0440 \u043F\u0440\u043E\u0444\u0438\u043B\u044F \u0442\u044F\u0433\u0438:
TCMotorSelPan.lbl.Ejectionchargedelay = \u0417\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u0432\u044B\u0431\u0440\u043E\u0441\u0430 \u0437\u0430\u0440\u044F\u0434\u0430:
-TCMotorSelPan.equalsIgnoreCase.None = \u041D\u0435\u0442
-TCMotorSelPan.lbl.NumberofsecondsorNone = (\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0438\u043B\u0438 "\u041D\u0435\u0442")
+TCMotorSelPan.lbl.Numberofseconds = (\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0438\u043B\u0438 "\u041D\u0435\u0442")
TCMotorSelPan.lbl.Designation = \u041E\u0431\u043E\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435:
TCMotorSelPan.lbl.Totalimpulse = \u041E\u0431\u0449\u0438\u0439 \u0438\u043C\u043F\u0443\u043B\u044C\u0441:
TCMotorSelPan.lbl.Avgthrust = \u0421\u0440\u0435\u0434\u043D\u044F\u044F \u0442\u044F\u0433\u0430:
@@ -1323,7 +1322,7 @@ TCMotorSelPan.lbl.Datapoints = \u0422\u043E\u0447\u043A\u0438 \u0434\u0430\u043D
TCMotorSelPan.lbl.Digest = \u0420\u0435\u0437\u044E\u043C\u0435:
TCMotorSelPan.title.Thrustcurve = \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u0442\u044F\u0433\u0438:
TCMotorSelPan.title.Thrust = \u0422\u044F\u0433\u0430
-TCMotorSelPan.delayBox.None = \u041D\u0435\u0442
+TCMotorSelPan.delayBox.Plugged = \u041D\u0435\u0442
TCMotorSelPan.noDescription = \u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442.
TCMotorSelPan.btn.checkAll = \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435
TCMotorSelPan.btn.checkNone = \u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440
diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties
index af3f83a22..8a7e912d8 100644
--- a/core/resources/l10n/messages_uk_UA.properties
+++ b/core/resources/l10n/messages_uk_UA.properties
@@ -1102,8 +1102,7 @@ TCMotorSelPan.MotorMountDimensions = Motor mount dimensions:
TCMotorSelPan.lbl.Search = Search:
TCMotorSelPan.lbl.Selectthrustcurve = Select thrust curve:
TCMotorSelPan.lbl.Ejectionchargedelay = Ejection charge delay:
-TCMotorSelPan.equalsIgnoreCase.None = None
-TCMotorSelPan.lbl.NumberofsecondsorNone = (Number of seconds or \"None\")
+TCMotorSelPan.lbl.Numberofseconds = (Number of seconds or \"None\")
TCMotorSelPan.lbl.Designation = Designation:
TCMotorSelPan.lbl.Totalimpulse = Total impulse:
TCMotorSelPan.lbl.Avgthrust = Avg. thrust:
@@ -1115,7 +1114,7 @@ TCMotorSelPan.lbl.Datapoints = Data points:
TCMotorSelPan.lbl.Digest = Digest:
TCMotorSelPan.title.Thrustcurve = Thrust curve:
TCMotorSelPan.title.Thrust = Thrust
-TCMotorSelPan.delayBox.None = Plugged (None)
+TCMotorSelPan.delayBox.Plugged = Plugged (None)
TCMotorSelPan.noDescription = No description available.
TCMotorSelPan.btn.checkAll = Select All
TCMotorSelPan.btn.checkNone = Clear All
diff --git a/core/resources/l10n/messages_zh_CN.properties b/core/resources/l10n/messages_zh_CN.properties
index c2ff76d5e..b4bff94ed 100644
--- a/core/resources/l10n/messages_zh_CN.properties
+++ b/core/resources/l10n/messages_zh_CN.properties
@@ -1188,8 +1188,7 @@ TCMotorSelPan.checkbox.hideSimilar = \u9690\u85CF\u76F8\u4F3C\u7684\u63A8\u
TCMotorSelPan.checkbox.hideUsed = \u9690\u85CF\u5DF2\u4F7F\u7528\u7684\u53D1\u52A8\u673A
TCMotorSelPan.checkbox.limitdiameter = \u76F4\u5F84\u4E0D\u8D85\u8FC7\u5F53\u524D\u53D1\u52A8\u673A\u5EA7\u76F4\u5F84
TCMotorSelPan.checkbox.limitlength = \u957F\u5EA6\u4E0D\u8D85\u8FC7\u5F53\u524D\u53D1\u52A8\u673A\u5EA7\u957F\u5EA6
-TCMotorSelPan.delayBox.None = \u65E0
-TCMotorSelPan.equalsIgnoreCase.None = \u65E0
+TCMotorSelPan.delayBox.Plugged = \u65E0
TCMotorSelPan.lbl.Avgthrust = \u5E73\u5747\u63A8\u529B:
TCMotorSelPan.lbl.Burntime = \u71C3\u70E7\u65F6\u95F4:
TCMotorSelPan.lbl.Datapoints = \u6570\u636E\u70B9:
@@ -1198,7 +1197,7 @@ TCMotorSelPan.lbl.Ejectionchargedelay = \u5F39\u5C04\u5EF6\u65F6:
TCMotorSelPan.lbl.Emptymass = \u71C3\u5C3D\u8D28\u91CF:
TCMotorSelPan.lbl.Launchmass = \u53D1\u5C04\u8D28\u91CF:
TCMotorSelPan.lbl.Maxthrust = \u6700\u5927\u63A8\u529B:
-TCMotorSelPan.lbl.NumberofsecondsorNone = (\u79D2\u6570\u6216"\u65E0")
+TCMotorSelPan.lbl.Numberofseconds = (\u79D2\u6570\u6216"\u65E0")
TCMotorSelPan.lbl.Search = \u641C\u7D22:
TCMotorSelPan.lbl.Selectthrustcurve = \u9009\u62E9\u63A8\u529B\u66F2\u7EBF:
TCMotorSelPan.lbl.Selrocketmotor = \u9009\u62E9\u706B\u7BAD\u53D1\u52A8\u673A:
diff --git a/core/resources/pix/eventicons/event-exception.png b/core/resources/pix/eventicons/event-exception.png
new file mode 100644
index 000000000..0cfd58596
Binary files /dev/null and b/core/resources/pix/eventicons/event-exception.png differ
diff --git a/core/src/net/sf/openrocket/aerodynamics/Warning.java b/core/src/net/sf/openrocket/aerodynamics/Warning.java
index 3c17c1e17..71946ca5b 100644
--- a/core/src/net/sf/openrocket/aerodynamics/Warning.java
+++ b/core/src/net/sf/openrocket/aerodynamics/Warning.java
@@ -382,6 +382,10 @@ public abstract class Warning {
////Jagged-edged fin predictions may be inaccurate.
public static final Warning JAGGED_EDGED_FIN = new Other(trans.get("Warning.JAGGED_EDGED_FIN"));
+ /** A Warning that the fins have a zero area. */
+ ////Fins with no area will not affect aerodynamics
+ public static final Warning ZERO_AREA_FIN = new Other(trans.get("Warning.ZERO_AREA_FIN"));
+
/** A Warning that simulation listeners have affected the simulation */
////Listeners modified the flight simulation
public static final Warning LISTENERS_AFFECTED = new Other(trans.get("Warning.LISTENERS_AFFECTED"));
diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java
index 53746d2e6..326c73a0d 100644
--- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java
+++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java
@@ -84,7 +84,9 @@ public class FinSetCalc extends RocketComponentCalc {
public void calculateNonaxialForces(FlightConditions conditions, Transformation transform,
AerodynamicForces forces, WarningSet warnings) {
- if (span < 0.001) {
+ warnings.addAll(geometryWarnings);
+
+ if (finArea < MathUtil.EPSILON) {
forces.setCm(0);
forces.setCN(0);
forces.setCNa(0);
@@ -96,12 +98,6 @@ public class FinSetCalc extends RocketComponentCalc {
forces.setCyaw(0);
return;
}
-
- if ((bodyRadius > 0) && (thickness > bodyRadius / 2)){
- // Add warnings (radius/2 == diameter/4)
- warnings.add(Warning.THICK_FIN);
- }
- warnings.addAll(geometryWarnings);
//////// Calculate CNa. /////////
@@ -241,18 +237,27 @@ public class FinSetCalc extends RocketComponentCalc {
Coordinate[] points = component.getFinPoints();
- // Check for jagged edges
+ // Check geometry
geometryWarnings.clear();
boolean down = false;
for (int i = 1; i < points.length; i++) {
if ((points[i].y > points[i - 1].y + 0.001) && down) {
- geometryWarnings.add(Warning.JAGGED_EDGED_FIN);
+ geometryWarnings.add(Warning.JAGGED_EDGED_FIN, component.toString());
break;
}
if (points[i].y < points[i - 1].y - 0.001) {
down = true;
}
}
+
+ if (finArea < MathUtil.EPSILON) {
+ geometryWarnings.add(Warning.ZERO_AREA_FIN, component.toString());
+ }
+
+ if ((bodyRadius > 0) && (thickness > bodyRadius / 2)){
+ // Add warnings (radius/2 == diameter/4)
+ geometryWarnings.add(Warning.THICK_FIN, component.toString());
+ }
// Calculate the chord lead and trail positions and length
@@ -365,7 +370,6 @@ public class FinSetCalc extends RocketComponentCalc {
macLead *= dy;
area *= dy;
rollSum *= dy;
-
macLength /= area;
macSpan /= area;
macLead /= area;
@@ -614,6 +618,11 @@ public class FinSetCalc extends RocketComponentCalc {
@Override
public double calculateFrictionCD(FlightConditions conditions, double componentCf, WarningSet warnings) {
+ // a fin with 0 area contributes no drag
+ if (finArea < MathUtil.EPSILON) {
+ return 0.0;
+ }
+
double cd = componentCf * (1 + 2 * thickness / macLength) * 2 * finArea / conditions.getRefArea();
return cd;
}
@@ -622,6 +631,11 @@ public class FinSetCalc extends RocketComponentCalc {
public double calculatePressureCD(FlightConditions conditions,
double stagnationCD, double baseCD, WarningSet warnings) {
+ // a fin with 0 area contributes no drag
+ if (finArea < MathUtil.EPSILON) {
+ return 0.0;
+ }
+
double mach = conditions.getMach();
double cd = 0;
diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java
index a94bd76e5..26a279fbf 100644
--- a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java
+++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java
@@ -58,44 +58,56 @@ public class RailButtonCalc extends RocketComponentCalc {
final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight();
final double refArea = outerArea - notchArea;
- // accumulate Cd contribution from each rail button
+ // accumulate Cd contribution from each rail button. If velocity is 0 just set CDmul to a value previously
+ // competed for velocity MathUtil.EPSILON and skip the loop to avoid division by 0
double CDmul = 0.0;
- for (int i = 0; i < button.getInstanceCount(); i++) {
-
- // compute boundary layer height at button location. I can't find a good reference for the
- // formula, e.g. https://aerospaceengineeringblog.com/boundary-layers/ simply says it's the
- // "scientific consensus".
- double x = (button.toAbsolute(instanceOffsets[i]))[0].x; // location of button
- double rex = calculateReynoldsNumber(x, conditions); // Reynolds number of button location
- double del = 0.37 * x / Math.pow(rex, 0.2); // Boundary layer thickness
-
- // compute mean airspeed over button
- // this assumes airspeed changes linearly through boundary layer
- // and that all parts of the railbutton contribute equally to Cd,
- // neither of which is true but both are plenty close enough for our purposes
-
- double mach;
- if (buttonHt > del) {
- // Case 1: button extends beyond boundary layer
- // Mean velocity is 1/2 rocket velocity up to limit of boundary layer,
- // full velocity after that
- mach = (buttonHt - 0.5*del) * conditions.getMach()/buttonHt;
- } else {
- // Case 2: button is entirely within boundary layer
- mach = MathUtil.map(buttonHt/2.0, 0, del, 0, conditions.getMach());
+ if (conditions.getMach() > MathUtil.EPSILON) {
+ for (int i = 0; i < button.getInstanceCount(); i++) {
+
+ // compute boundary layer height at button location. I can't find a good reference for the
+ // formula, e.g. https://aerospaceengineeringblog.com/boundary-layers/ simply says it's the
+ // "scientific consensus".
+ double x = (button.toAbsolute(instanceOffsets[i]))[0].x; // location of button
+ double rex = calculateReynoldsNumber(x, conditions); // Reynolds number of button location
+ double del = 0.37 * x / Math.pow(rex, 0.2); // Boundary layer thickness
+
+ // compute mean airspeed over button
+ // this assumes airspeed changes linearly through boundary layer
+ // and that all parts of the railbutton contribute equally to Cd,
+ // neither of which is true but both are plenty close enough for our purposes
+
+ double mach;
+ if (buttonHt > del) {
+ // Case 1: button extends beyond boundary layer
+ // Mean velocity is 1/2 rocket velocity up to limit of boundary layer,
+ // full velocity after that
+ mach = (buttonHt - 0.5*del) * conditions.getMach()/buttonHt;
+ } else {
+ // Case 2: button is entirely within boundary layer
+ mach = MathUtil.map(buttonHt/2.0, 0, del, 0, conditions.getMach());
+ }
+
+ // look up Cd as function of speed. It's pretty constant as a function of Reynolds
+ // number when slow, so we can just use a function of Mach number
+ double cd = MathUtil.interpolate(cdDomain, cdRange, mach);
+
+ // Since later drag force calculations don't consider boundary layer, compute "effective Cd"
+ // based on rocket velocity
+ cd = cd * MathUtil.pow2(mach)/MathUtil.pow2(conditions.getMach());
+
+ // add to CDmul
+ CDmul += cd;
+
}
- // look up Cd as function of speed. It's pretty constant as a function of Reynolds
- // number when slow, so we can just use a function of Mach number
- double cd = MathUtil.interpolate(cdDomain, cdRange, mach);
-
- // Since later drag force calculations don't consider boundary layer, compute "effective Cd"
- // based on rocket velocity
- cd = cd * MathUtil.pow2(mach)/MathUtil.pow2(conditions.getMach());
+ // since we'll be multiplying by the instance count up in BarrowmanCalculator,
+ // we want to return the mean CD instead of the total
+ CDmul /= button.getInstanceCount();
- // add to CDmul
- CDmul += cd;
- }
+ } else {
+ // value at velocity of MathUtil.EPSILON
+ CDmul = 8.786395072609939E-4;
+ }
return CDmul * stagnationCD * refArea / conditions.getRefArea();
}
diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java
index 9ade10f5e..068fc91c8 100644
--- a/core/src/net/sf/openrocket/document/Simulation.java
+++ b/core/src/net/sf/openrocket/document/Simulation.java
@@ -466,7 +466,7 @@ public class Simulation implements ChangeSource, Cloneable {
/**
* Return true if this simulation contains plottable flight data.
*
- * @return
+ * @return true if this simulation contains plottable flight data.
*/
public boolean hasSimulationData() {
FlightData data = getSimulatedData();
@@ -478,6 +478,15 @@ public class Simulation implements ChangeSource, Cloneable {
}
return true;
}
+
+ /**
+ * Return true if this simulation contains summary flight data.
+ * @return true if this simulation contains summary flight data.
+ */
+ public boolean hasSummaryData() {
+ FlightData data = getSimulatedData();
+ return data != null;
+ }
/**
* Returns a copy of this simulation suitable for cut/copy/paste operations.
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java
index 1afab2aab..2750285dc 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java
@@ -61,7 +61,7 @@ public class OpenRocketLoader extends AbstractRocketLoader {
}
// If we saved data for a simulation before, we'll use that as our default option this time
- boolean saveData = false;
+ // Also, updaet all the sims' modIDs to agree with flight config
for (Simulation s : doc.getSimulations()) {
s.syncModID(); // The config's modID can be out of sync with the simulation's after the whole loading process
if (s.getStatus() == Simulation.Status.EXTERNAL ||
@@ -79,8 +79,6 @@ public class OpenRocketLoader extends AbstractRocketLoader {
continue;
doc.getDefaultStorageOptions().setSaveSimulationData(true);
- break;
-
}
doc.getDefaultStorageOptions().setExplicitlySet(false);
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
index 11f4af969..f24edfbc7 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java
@@ -109,6 +109,13 @@ class SingleSimulationHandler extends AbstractElementHandler {
public void endHandler(String element, HashMap attributes,
String content, WarningSet warnings) {
+ String s = attributes.get("status");
+ Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
+ if (status == null) {
+ warnings.add("Simulation status unknown, assuming outdated.");
+ status = Simulation.Status.OUTDATED;
+ }
+
SimulationOptions options;
FlightConfigurationId idToSet= FlightConfigurationId.ERROR_FCID;
if (conditionHandler != null) {
@@ -123,20 +130,19 @@ class SingleSimulationHandler extends AbstractElementHandler {
name = "Simulation";
// If the simulation was saved with flight data (which may just be a summary)
- // mark it as loaded from the file else as not simulated. We're ignoring the
- // simulation status attribute, since (1) it really isn't relevant now, and (2)
- // sim summaries are getting marked as not simulated when they're saved
+ // mark it as loaded from the file else as not simulated. If outdated data was saved,
+ // it'll be marked as outdated (creating a new status for "loaded but outdated" seems
+ // excessive, and the fact that it's outdated is the more important)
FlightData data;
if (dataHandler == null)
data = null;
else
data = dataHandler.getFlightData();
- Simulation.Status status;
- if (data != null) {
- status = Status.LOADED;
- } else {
+ if (data == null) {
status = Status.NOT_SIMULATED;
+ } else if (status != Status.OUTDATED) {
+ status = Status.LOADED;
}
Simulation simulation = new Simulation(doc, doc.getRocket(), status, name,
diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
index 6c32cb151..6838c4a2b 100644
--- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
+++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
@@ -86,13 +86,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
}
- // Can't calculate stability
- if (currentStatus.getSimulationConditions().getAerodynamicCalculator()
- .getCP(currentStatus.getConfiguration(),
- new FlightConditions(currentStatus.getConfiguration()),
- new WarningSet()).weight < MathUtil.EPSILON)
- throw new SimulationException(trans.get("BasicEventSimulationEngine.error.cantCalculateStability"));
-
// Problems that let us simulate, but result is likely bad
// No recovery device
@@ -157,6 +150,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = currentStatus.getRocketVelocity();
try {
+
+ checkGeometry(currentStatus);
// Start the simulation
while (handleEvents()) {
@@ -284,6 +279,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
flightData.getWarningSet().addAll(currentStatus.getWarnings());
e.setFlightData(flightData);
+ e.setFlightDataBranch(currentStatus.getFlightData());
throw e;
}
@@ -495,7 +491,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
SimulationStatus boosterStatus = new SimulationStatus(currentStatus);
// Prepare the new simulation branch
- boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), FlightDataType.TYPE_TIME));
+ boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), currentStatus.getFlightData()));
+ boosterStatus.getFlightData().addEvent(event);
// Mark the current status as having dropped the current stage and all stages below it
currentStatus.getConfiguration().clearStagesBelow( stageNumber);
@@ -504,6 +501,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
boosterStatus.getConfiguration().clearStagesAbove(stageNumber);
toSimulate.push(boosterStatus);
+
+ // Make sure upper stages can still be simulated
+ checkGeometry(currentStatus);
+
log.info(String.format("==>> @ %g; from Branch: %s ---- Branching: %s ---- \n",
currentStatus.getSimulationTime(),
currentStatus.getFlightData().getBranchName(), boosterStatus.getFlightData().getBranchName()));
@@ -665,8 +666,24 @@ public class BasicEventSimulationEngine implements SimulationEngine {
return null;
}
}
-
-
+
+ // we need to check geometry to make sure we can simulation the active
+ // stages in a simulation branch when the branch starts executing, and
+ // whenever a stage separation occurs
+ private void checkGeometry(SimulationStatus currentStatus) throws SimulationException {
+
+ // Active stages have total length of 0.
+ if (currentStatus.getConfiguration().getLengthAerodynamic() < MathUtil.EPSILON) {
+ throw new SimulationException(trans.get("BasicEventSimulationEngine.error.activeLengthZero"));
+ }
+
+ // Can't calculate stability
+ if (currentStatus.getSimulationConditions().getAerodynamicCalculator()
+ .getCP(currentStatus.getConfiguration(),
+ new FlightConditions(currentStatus.getConfiguration()),
+ new WarningSet()).weight < MathUtil.EPSILON)
+ throw new SimulationException(trans.get("BasicEventSimulationEngine.error.cantCalculateStability"));
+ }
private void checkNaN() throws SimulationException {
double d = 0;
diff --git a/core/src/net/sf/openrocket/simulation/DefaultSimulationOptionFactory.java b/core/src/net/sf/openrocket/simulation/DefaultSimulationOptionFactory.java
index c68cff0c2..31f2451d9 100644
--- a/core/src/net/sf/openrocket/simulation/DefaultSimulationOptionFactory.java
+++ b/core/src/net/sf/openrocket/simulation/DefaultSimulationOptionFactory.java
@@ -46,7 +46,7 @@ public class DefaultSimulationOptionFactory {
defaults.setISAAtmosphere(prefs.getBoolean(SIMCONDITION_ATMOS_STD, defaults.isISAAtmosphere()));
defaults.setLaunchTemperature(prefs.getDouble(SIMCONDITION_ATMOS_TEMP, defaults.getLaunchTemperature()));
- defaults.setLaunchPressure(prefs.getDouble(SIMCONDITION_ATMOS_PRESSURE, defaults.getLaunchTemperature()));
+ defaults.setLaunchPressure(prefs.getDouble(SIMCONDITION_ATMOS_PRESSURE, defaults.getLaunchPressure()));
defaults.setLaunchIntoWind(prefs.getBoolean(SIMCONDITION_ROD_INTO_WIND, defaults.getLaunchIntoWind()));
defaults.setLaunchRodLength(prefs.getDouble(SIMCONDITION_ROD_LENGTH, defaults.getLaunchRodLength()));
diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
index c7f71f7fc..77bc60f56 100644
--- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
+++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
@@ -75,6 +75,26 @@ public class FlightDataBranch implements Monitorable {
maxValues.put(t, Double.NaN);
}
}
+
+ /**
+ * Make a flight data branch with one data point copied from its parent. Intended for use
+ * when creating a new branch upon stage separation, so the data at separation is present
+ * in both branches (and if the new branch has an immediate exception, it can be plotted)
+ */
+ public FlightDataBranch(String branchName, FlightDataBranch parent) {
+ this.branchName = branchName;
+
+ // need to have at least one type to set up values
+ values.put(FlightDataType.TYPE_TIME, new ArrayList());
+ minValues.put(FlightDataType.TYPE_TIME, Double.NaN);
+ maxValues.put(FlightDataType.TYPE_TIME, Double.NaN);
+
+ // copy all values into new FlightDataBranch
+ this.addPoint();
+ for (FlightDataType t : parent.getTypes()) {
+ this.setValue(t, parent.getLast(t));
+ }
+ }
/**
* Makes an 'empty' flight data branch which has no data but all built in data types are defined.
diff --git a/core/src/net/sf/openrocket/simulation/exception/SimulationException.java b/core/src/net/sf/openrocket/simulation/exception/SimulationException.java
index e0e8ee8ec..720eadce0 100644
--- a/core/src/net/sf/openrocket/simulation/exception/SimulationException.java
+++ b/core/src/net/sf/openrocket/simulation/exception/SimulationException.java
@@ -1,10 +1,12 @@
package net.sf.openrocket.simulation.exception;
import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.FlightDataBranch;
public class SimulationException extends Exception {
private FlightData flightData = null;
+ private FlightDataBranch flightDataBranch = null;
public SimulationException() {
@@ -29,4 +31,13 @@ public class SimulationException extends Exception {
public FlightData getFlightData() {
return flightData;
}
+
+ public void setFlightDataBranch(FlightDataBranch f) {
+ flightDataBranch = f;
+ }
+
+ public FlightDataBranch getFlightDataBranch() {
+ return flightDataBranch;
+ }
+
}
diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java
index 08f52bea4..2a1c9397e 100644
--- a/core/src/net/sf/openrocket/util/TestRockets.java
+++ b/core/src/net/sf/openrocket/util/TestRockets.java
@@ -1103,6 +1103,26 @@ public class TestRockets {
return rocket;
}
+ // Several simulations need the Falcon9Heavy, but with fins added to the
+ // core stage (without them, there is a simulation exception at stage separation
+ // This method is intended to add those fins to the F9H, but will in fact
+ // add them to the second stage of a rocket
+ public static void addCoreFins(Rocket rocket) {
+ final int bodyFinCount = 4;
+ final double bodyFinRootChord = 0.05;
+ final double bodyFinTipChord = bodyFinRootChord;
+ final double bodyFinHeight = 0.025;
+ final double bodyFinSweep = 0.0;
+ final AxialMethod bodyFinAxialMethod = AxialMethod.BOTTOM;
+ final double bodyFinAxialOffset = 0.0;
+
+ final TrapezoidFinSet finSet = new TrapezoidFinSet(bodyFinCount, bodyFinRootChord, bodyFinTipChord, bodyFinSweep, bodyFinHeight);
+ finSet.setName("Body Tube FinSet");
+ finSet.setAxialMethod(bodyFinAxialMethod);
+
+ rocket.getChild(1).getChild(0).addChild(finSet);
+ }
+
// This is a simple four-fin rocket with large endplates on the
// fins, for testing CG and CP calculations with fins on pods.
// not a complete rocket (no motor mount nor recovery system)
diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
index ca4aaa3b4..3b1a66d20 100644
--- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
+++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java
@@ -20,11 +20,13 @@ import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.ParallelStage;
import net.sf.openrocket.rocketcomponent.PodSet;
+import net.sf.openrocket.rocketcomponent.RailButton;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.TestRockets;
public class BarrowmanCalculatorTest {
@@ -496,5 +498,70 @@ public class BarrowmanCalculatorTest {
testCP = testCalc.getCP(testConfig, testConditions, warnings).x;
assertEquals("should be warning from podset airframe overlap", 1, warnings.size());
}
+
+ /**
+ * Tests railbutton drag. Really is testing instancing more than actual drag calculations, and making
+ * sure we don't divide by 0 when not moving
+ */
+ @Test
+ public void testRailButtonDrag() {
+ // minimal rocket with nothing on it but two railbuttons
+ final Rocket rocket = new Rocket();
+ final AxialStage stage = new AxialStage();
+ rocket.addChild(stage);
+
+ // phantom tubes have no drag to confuse things
+ final BodyTube phantom = new BodyTube();
+ phantom.setOuterRadius(0);
+ stage.addChild(phantom);
+
+ // set up test environment
+ WarningSet warnings = new WarningSet();
+ final FlightConfiguration config = rocket.getSelectedConfiguration();
+ final FlightConditions conditions = new FlightConditions(config);
+ final BarrowmanCalculator calc = new BarrowmanCalculator();
+
+ // part 1: instancing
+
+ // Put two individual railbuttons and get their CD
+ final RailButton button1 = new RailButton();
+ button1.setInstanceCount(1);
+ button1.setAxialOffset(1.0);
+ phantom.addChild(button1);
+
+ final RailButton button2 = new RailButton();
+ button2.setInstanceCount(1);
+ button2.setAxialOffset(2.0);
+ phantom.addChild(button2);
+
+ final AerodynamicForces individualForces = calc.getAerodynamicForces(config, conditions, warnings);
+ final double individualCD = individualForces.getCD();
+
+ // get rid of individual buttons and put in a railbutton set with two instances at same locations as original
+ // railbuttons
+ phantom.removeChild(button1);
+ phantom.removeChild(button2);
+
+ final RailButton buttons = new RailButton();
+ buttons.setInstanceCount(2);
+ buttons.setAxialOffset(1.0);
+ buttons.setInstanceSeparation(1.0);
+
+ final AerodynamicForces pairForces = calc.getAerodynamicForces(config, conditions, warnings);
+ final double pairCD = pairForces.getCD();
+
+ assertEquals("two individual railbuttons should have same CD as a pair", individualCD, pairCD, EPSILON);
+
+ // part 2: test at Mach 0
+ conditions.setMach(MathUtil.EPSILON);
+ final AerodynamicForces epsForces = calc.getAerodynamicForces(config, conditions, warnings);
+ final double epsCD = epsForces.getCD();
+
+ conditions.setMach(0);
+ final AerodynamicForces zeroForces = calc.getAerodynamicForces(config, conditions, warnings);
+ final double zeroCD = zeroForces.getCD();
+ assertEquals("drag at mach 0 should equal drag at mach MathUtil.EPSILON", epsCD, zeroCD, EPSILON);
+ }
}
+
diff --git a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java
index 52014e4f1..6c613ee32 100644
--- a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java
+++ b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java
@@ -22,6 +22,7 @@ import net.sf.openrocket.database.motor.MotorDatabase;
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
+import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.file.GeneralRocketLoader;
import net.sf.openrocket.file.RocketLoadException;
@@ -264,7 +265,64 @@ public class OpenRocketSaverTest {
// TODO: fix estimateFileSize so that it's a lot more accurate
}
-
+
+ /**
+ * Test sim status with/without sim data in file.
+ */
+ @Test
+ public void TestSimStatus() {
+ Rocket rocket = TestRockets.makeEstesAlphaIII();
+ OpenRocketDocument rocketDoc = OpenRocketDocumentFactory.createDocumentFromRocket(rocket);
+
+ // Hook up some simulations.
+ // First sim will not have options set
+ Simulation sim1 = new Simulation(rocket);
+ rocketDoc.addSimulation(sim1);
+
+ // Second sim has options, but hasn't been simulated
+ Simulation sim2 = new Simulation(rocket);
+ sim2.getOptions().setISAAtmosphere(true);
+ sim2.getOptions().setTimeStep(0.05);
+ sim2.setFlightConfigurationId(TestRockets.TEST_FCID_0);
+ rocketDoc.addSimulation(sim2);
+
+ // Third sim has been executed
+ Simulation sim3 = new Simulation(rocket);
+ sim3.getOptions().setISAAtmosphere(true);
+ sim3.getOptions().setTimeStep(0.05);
+ sim3.setFlightConfigurationId(TestRockets.TEST_FCID_0);
+ try {
+ sim3.simulate();
+ } catch (Exception e) {
+ fail(e.toString());
+ }
+ rocketDoc.addSimulation(sim3);
+
+ // Fourth sim has been executed, then configuration changed
+ Simulation sim4 = new Simulation(rocket);
+ sim4.getOptions().setISAAtmosphere(true);
+ sim4.getOptions().setTimeStep(0.05);
+ sim4.setFlightConfigurationId(TestRockets.TEST_FCID_0);
+ try {
+ sim4.simulate();
+ } catch (Exception e) {
+ fail(e.toString());
+ }
+ sim4.getOptions().setTimeStep(0.1);
+ rocketDoc.addSimulation(sim4);
+
+ // save, then load document
+ StorageOptions options = new StorageOptions();
+ options.setSaveSimulationData(true);
+
+ File file = saveRocket(rocketDoc, options);
+ OpenRocketDocument rocketDocLoaded = loadRocket(file.getPath());
+
+ assertEquals(Simulation.Status.CANT_RUN, rocketDocLoaded.getSimulations().get(0).getStatus());
+ assertEquals(Simulation.Status.NOT_SIMULATED, rocketDocLoaded.getSimulations().get(1).getStatus());
+ assertEquals(Simulation.Status.LOADED, rocketDocLoaded.getSimulations().get(2).getStatus());
+ assertEquals(Simulation.Status.OUTDATED, rocketDocLoaded.getSimulations().get(3).getStatus());
+ }
////////////////////////////////
// Tests for File Version 1.7 //
@@ -276,7 +334,8 @@ public class OpenRocketSaverTest {
assertEquals(108, getCalculatedFileVersion(rocketDoc));
}
-
+
+ ////////////////////////////////
/*
* Utility Functions
*/
diff --git a/core/test/net/sf/openrocket/simulation/DisableStageTest.java b/core/test/net/sf/openrocket/simulation/DisableStageTest.java
index f92c634ea..42b0f77b7 100644
--- a/core/test/net/sf/openrocket/simulation/DisableStageTest.java
+++ b/core/test/net/sf/openrocket/simulation/DisableStageTest.java
@@ -148,9 +148,13 @@ public class DisableStageTest extends BaseTestCase {
@Test
public void testBooster1() {
//// Test disabling the stage
- Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed
- Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled
+ Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed
+ TestRockets.addCoreFins(rocketRemoved);
+
+ Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled
+ TestRockets.addCoreFins(rocketDisabled);
+
FlightConfigurationId fcid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1);
int stageNr = 2; // Stage 2 is the Parallel Booster Stage
rocketRemoved.getChild(1).getChild(0).removeChild(0); // Remove the Parallel Booster Stage
@@ -171,6 +175,8 @@ public class DisableStageTest extends BaseTestCase {
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeFalcon9Heavy();
+ TestRockets.addCoreFins(rocketOriginal);
+
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(fcid);
simOriginal.getOptions().setISAAtmosphere(true);
@@ -188,7 +194,10 @@ public class DisableStageTest extends BaseTestCase {
public void testBooster2() {
//// Test disabling the stage
Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed
+ TestRockets.addCoreFins(rocketRemoved);
+
Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled
+ TestRockets.addCoreFins(rocketDisabled);
FlightConfigurationId fid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1);
int stageNr = 1; // Stage 1 is the Parallel Booster Stage's parent stage
@@ -225,6 +234,8 @@ public class DisableStageTest extends BaseTestCase {
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeFalcon9Heavy();
+ TestRockets.addCoreFins(rocketOriginal);
+
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(fid);
simOriginal.getOptions().setISAAtmosphere(true);
diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
index 74f573b98..ddb6993ca 100644
--- a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
+++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
@@ -85,6 +85,8 @@ public class FlightEventsTest extends BaseTestCase {
@Test
public void testMultiStage() throws SimulationException {
final Rocket rocket = TestRockets.makeFalcon9Heavy();
+ TestRockets.addCoreFins(rocket);
+
final Simulation sim = new Simulation(rocket);
sim.getOptions().setISAAtmosphere(true);
sim.getOptions().setTimeStep(0.05);
@@ -98,32 +100,49 @@ public class FlightEventsTest extends BaseTestCase {
final int branchCount = sim.getSimulatedData().getBranchCount();
assertEquals(" Multi-stage simulation invalid branch count", 3, branchCount);
+ final AxialStage coreStage = rocket.getStage(1);
+ final ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
+ final InnerTube boosterMotorTubes = (InnerTube) boosterStage.getChild(1).getChild(0);
+ final BodyTube coreBody = (BodyTube) coreStage.getChild(0);
+
+ // events whose time is too variable to check are given a time of 1200
for (int b = 0; b < 3; b++) {
- final FlightEvent.Type[] expectedEventTypes;
- final double[] expectedEventTimes;
+ FlightEvent[] expectedEvents;
final RocketComponent[] expectedSources;
switch (b) {
case 0:
- expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.LAUNCH, FlightEvent.Type.IGNITION, FlightEvent.Type.IGNITION,
- FlightEvent.Type.LIFTOFF, FlightEvent.Type.LAUNCHROD, FlightEvent.Type.APOGEE,
- FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.STAGE_SEPARATION,
- FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.STAGE_SEPARATION,
- FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT, FlightEvent.Type.SIMULATION_END};
- expectedEventTimes = new double[]{0.0, 0.0, 0.0, 0.1225, 0.125, 1.735, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0}; // Tumble and ground hit time are too variable, so don't include it
- final AxialStage coreStage = rocket.getStage(1);
- final ParallelStage boosterStage = (ParallelStage) rocket.getStage(2);
- final InnerTube boosterMotorTubes = (InnerTube) boosterStage.getChild(1).getChild(0);
- final BodyTube coreBody = (BodyTube) coreStage.getChild(0);
- expectedSources = new RocketComponent[]{rocket, boosterMotorTubes, coreBody, null, null, rocket,
- boosterMotorTubes, boosterStage, boosterStage, coreBody, coreStage, coreStage,
- null, null, null};
+ expectedEvents = new FlightEvent[] {
+ new FlightEvent(FlightEvent.Type.LAUNCH, 0.0, rocket),
+ new FlightEvent(FlightEvent.Type.IGNITION, 0.0, boosterMotorTubes),
+ new FlightEvent(FlightEvent.Type.IGNITION, 0.0, coreBody),
+ new FlightEvent(FlightEvent.Type.LIFTOFF, 0.1225, null),
+ new FlightEvent(FlightEvent.Type.LAUNCHROD, 0.125, null),
+ new FlightEvent(FlightEvent.Type.APOGEE, 1.86, rocket),
+ new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, boosterMotorTubes),
+ new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, boosterStage),
+ new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage),
+ new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, coreBody),
+ new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, coreStage),
+ new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, coreStage),
+ new FlightEvent(FlightEvent.Type.TUMBLE, 2.4127, null),
+ new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
+ new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
+ };
break;
case 1:
+ expectedEvents = new FlightEvent[] {
+ new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, coreStage),
+ new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
+ new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
+ };
+ break;
case 2:
- expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT,
- FlightEvent.Type.SIMULATION_END};
- expectedEventTimes = new double[]{}; // Tumble and ground hit time are too variable, so don't include it
- expectedSources = new RocketComponent[]{null, null, null};
+ expectedEvents = new FlightEvent[] {
+ new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage),
+ new FlightEvent(FlightEvent.Type.TUMBLE, 3.551, null),
+ new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
+ new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
+ };
break;
default:
throw new IllegalStateException("Invalid branch number " + b);
@@ -131,28 +150,28 @@ public class FlightEventsTest extends BaseTestCase {
// Test event count
final FlightDataBranch branch = sim.getSimulatedData().getBranch(b);
- final List eventList = branch.getEvents();
- final List eventTypes = eventList.stream().map(FlightEvent::getType).collect(Collectors.toList());
- assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events", expectedEventTypes.length, eventTypes.size());
+ final FlightEvent[] events = (FlightEvent[]) branch.getEvents().toArray(new FlightEvent[0]);
+ for (int i = 0; i < events.length; i++) {
+ System.out.println("branch " + b + " index " + i + " event " + events[i]);
+ }
+ assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events", expectedEvents.length, events.length);
- // Test that all expected events are present, and in the right order
- for (int i = 0; i < expectedEventTypes.length; i++) {
- assertSame(" Flight type " + expectedEventTypes[i] + ", branch " + b + " not found in multi-stage simulation",
- expectedEventTypes[i], eventTypes.get(i));
- }
+ // Test that all expected events are present, in the right order, at the right time, from the right sources
+ for (int i = 0; i < events.length; i++) {
+ final FlightEvent expected = expectedEvents[i];
+ final FlightEvent actual = events[i];
+ assertSame("Branch " + b + " FlightEvent " + i + " type " + expected.getType() + " not found; FlightEvent " + actual.getType() + " found instead",
+ expected.getType(), actual.getType());
- // Test that the event times are correct
- for (int i = 0; i < expectedEventTimes.length; i++) {
- assertEquals(" Flight type " + expectedEventTypes[i] + " has wrong time",
- expectedEventTimes[i], eventList.get(i).getTime(), EPSILON);
- }
+ if (1200 != expected.getTime()) {
+ assertEquals("Branch " + b + " FlightEvent " + i + " type " + expected.getType() + " has wrong time",
+ expected.getTime(), actual.getTime(), EPSILON);
+ }
- // Test that the event sources are correct
- for (int i = 0; i < expectedSources.length; i++) {
- assertEquals(" Flight type " + expectedEventTypes[i] + " has wrong source",
- expectedSources[i], eventList.get(i).getSource());
+ // Test that the event sources are correct
+ assertEquals("Branch " + b + " FlightEvent " + i + " type " + expected.getType() + " has wrong source",
+ expected.getSource(), actual.getSource());
}
}
}
-
}
diff --git a/core/test/net/sf/openrocket/simulation/SimulationConditionsTest.java b/core/test/net/sf/openrocket/simulation/SimulationConditionsTest.java
new file mode 100644
index 000000000..78c60539d
--- /dev/null
+++ b/core/test/net/sf/openrocket/simulation/SimulationConditionsTest.java
@@ -0,0 +1,79 @@
+package net.sf.openrocket.simulation;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import net.sf.openrocket.ServicesForTesting;
+import net.sf.openrocket.formatting.RocketDescriptor;
+import net.sf.openrocket.formatting.RocketDescriptorImpl;
+import net.sf.openrocket.l10n.DebugTranslator;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.plugin.PluginModule;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.startup.MockPreferences;
+import net.sf.openrocket.startup.Preferences;
+import net.sf.openrocket.util.MathUtil;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class SimulationConditionsTest {
+ private final static double EPSILON = MathUtil.EPSILON;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ Module applicationModule = new PreferencesModule();
+ Module debugTranslator = new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(Translator.class).toInstance(new DebugTranslator(null));
+ }
+
+ };
+ Module pluginModule = new PluginModule();
+ Injector injector = Guice.createInjector(Modules.override(applicationModule).with(debugTranslator), pluginModule);
+ Application.setInjector(injector);
+ }
+
+ @Test
+ public void testDefaultSimulationOptionFactory() {
+ Application.getInjector().injectMembers(this);
+ DefaultSimulationOptionFactory factory = Application.getInjector().getInstance(DefaultSimulationOptionFactory.class);
+ SimulationOptions options = factory.getDefault();
+ assertNotNull(options);
+
+ assertEquals(28.61, options.getLaunchLatitude(), EPSILON);
+ assertEquals(0.0, options.getLaunchAltitude(), EPSILON);
+ assertEquals(-80.60, options.getLaunchLongitude(), EPSILON);
+ assertTrue(options.isISAAtmosphere());
+ assertEquals(288.15, options.getLaunchTemperature(), EPSILON);
+ assertEquals(101325, options.getLaunchPressure(), EPSILON);
+ assertEquals(1.0, options.getLaunchRodLength(), EPSILON);
+ assertEquals(Math.PI / 2, options.getLaunchRodDirection(), EPSILON);
+ assertEquals(0.0, options.getLaunchRodAngle(), EPSILON);
+ assertTrue(options.getLaunchIntoWind());
+ assertEquals(Math.PI / 2, options.getWindDirection(), EPSILON);
+ assertEquals(0.1, options.getWindTurbulenceIntensity(), EPSILON);
+ assertEquals(2.0, options.getWindSpeedAverage(), EPSILON);
+ assertEquals(0.2, options.getWindSpeedDeviation(), EPSILON);
+
+ assertEquals(0.05, options.getTimeStep(), EPSILON);
+ assertEquals(3 * Math.PI / 180, options.getMaximumStepAngle(), EPSILON);
+
+ }
+
+ private static class PreferencesModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(Preferences.class).to(MockPreferences.class);
+ bind(Translator.class).toProvider(ServicesForTesting.TranslatorProviderForTesting.class);
+ bind(RocketDescriptor.class).to(RocketDescriptorImpl.class);
+ }
+ }
+}
diff --git a/core/test/net/sf/openrocket/startup/MockPreferences.java b/core/test/net/sf/openrocket/startup/MockPreferences.java
index eabfe18d9..8b9bb6685 100644
--- a/core/test/net/sf/openrocket/startup/MockPreferences.java
+++ b/core/test/net/sf/openrocket/startup/MockPreferences.java
@@ -3,11 +3,13 @@ package net.sf.openrocket.startup;
import java.util.Set;
import java.util.prefs.BackingStoreException;
+import com.google.inject.Singleton;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
import net.sf.openrocket.util.BugException;
+@Singleton
public class MockPreferences extends Preferences {
private final String NODENAME = "OpenRocket-test-mock";
diff --git a/swing/resources/datafiles/examples/Airstart timing.ork b/swing/resources/datafiles/examples/Airstart timing.ork
index 23f271285..667446242 100644
Binary files a/swing/resources/datafiles/examples/Airstart timing.ork and b/swing/resources/datafiles/examples/Airstart timing.ork differ
diff --git a/swing/resources/datafiles/examples/Dual parachute deployment.ork b/swing/resources/datafiles/examples/Dual parachute deployment.ork
index ba46c4944..237457789 100644
Binary files a/swing/resources/datafiles/examples/Dual parachute deployment.ork and b/swing/resources/datafiles/examples/Dual parachute deployment.ork differ
diff --git a/swing/resources/datafiles/examples/Parallel booster staging.ork b/swing/resources/datafiles/examples/Parallel booster staging.ork
index ac901c1c0..b7d3976d7 100644
Binary files a/swing/resources/datafiles/examples/Parallel booster staging.ork and b/swing/resources/datafiles/examples/Parallel booster staging.ork differ
diff --git a/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java b/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java
index 0906b22ed..7aeef0a25 100644
--- a/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java
+++ b/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java
@@ -87,9 +87,11 @@ public class SimulationTableCSVExport {
* Generate the CSV data from the simulation table
* @param fieldSep The field separator to use in the CSV file.
* @param precision The number of decimal places to use in the CSV file.
- * @return
+ * @param isExponentialNotation If true, use exponential notation for numbers.
+ * @param onlySelected If true, only export the selected rows in the table.
+ * @return The CSV data as one string block.
*/
- public String generateCSVDate(String fieldSep, int precision) {
+ public String generateCSVDate(String fieldSep, int precision, boolean isExponentialNotation, boolean onlySelected) {
int modelColumnCount = simulationTableModel.getColumnCount();
int modelRowCount = simulationTableModel.getRowCount();
populateColumnNameToUnitsHashTable();
@@ -97,7 +99,7 @@ public class SimulationTableCSVExport {
String CSVSimResultString;
// Obtain the column titles for the first row of the CSV
ArrayList rowColumnElement = new ArrayList<>();
- for (int j = 1; j curveSelectionBox;
private final DefaultComboBoxModel curveSelectionModel;
+ private final JLabel ejectionChargeDelayLabel;
private final JComboBox delayBox;
private final JLabel nrOfMotorsLabel;
@@ -172,17 +173,22 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
// Ejection charge delay:
{
- panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay")));
+ ejectionChargeDelayLabel = new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay"));
+ panel.add(ejectionChargeDelayLabel);
delayBox = new JComboBox();
delayBox.setEditable(true);
delayBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
-
String sel = (String) delayBox.getSelectedItem();
- //// None
- if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.equalsIgnoreCase.None"))) {
+ if (sel == null) {
+ log.debug("Selected charge delay is null");
+ return;
+ }
+ //// Plugged (or None)
+ if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.delayBox.Plugged")) ||
+ sel.equalsIgnoreCase(trans.get("TCMotorSelPan.delayBox.PluggedNone"))) {
selectedDelay = Motor.PLUGGED_DELAY;
} else {
try {
@@ -194,8 +200,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
}
});
panel.add(delayBox, "growx,wrap");
- //// (Number of seconds or \"None\")
- panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "skip, wrap");
+ //// (or type in desired delay in seconds)
+ panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.Numberofseconds"), -3), "skip, wrap");
setDelays(false);
}
@@ -507,11 +513,16 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
curveSelectionModel.removeAllElements();
curveSelectionBox.setEnabled(false);
curveSelectionLabel.setEnabled(false);
+ ejectionChargeDelayLabel.setEnabled(false);
+ delayBox.setEnabled(false);
motorInformationPanel.clearData();
table.clearSelection();
return;
}
+ ejectionChargeDelayLabel.setEnabled(true);
+ delayBox.setEnabled(true);
+
// Check which thrust curves to display
List motors = getFilteredCurves();
final int index = motors.indexOf(selectedMotor);
@@ -562,7 +573,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
private void updateNrOfMotors() {
if (table != null && nrOfMotorsLabel != null) {
int rowCount = table.getRowCount();
- String motorCount = "None";
+ String motorCount = trans.get("TCMotorSelPan.lbl.nrOfMotors.None");
if (rowCount > 0) {
motorCount = String.valueOf(rowCount);
}
@@ -574,7 +585,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
private void scrollSelectionVisible() {
if (selectedMotorSet != null) {
int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
- //System.out.println("index=" + index);
table.getSelectionModel().setSelectionInterval(index, index);
Rectangle rect = table.getCellRect(index, 0, true);
rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
@@ -663,47 +673,45 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
*/
private void setDelays(boolean reset) {
if (selectedMotor == null) {
-
- //// None
- delayBox.setModel(new DefaultComboBoxModel(new String[] { trans.get("TCMotorSelPan.delayBox.None") }));
- delayBox.setSelectedIndex(0);
-
+ //// Display nothing
+ delayBox.setModel(new DefaultComboBoxModel<>(new String[] {}));
} else {
-
List delays = selectedMotorSet.getDelays();
- String[] delayStrings = new String[delays.size()];
+ boolean containsPlugged = delays.contains(Motor.PLUGGED_DELAY);
+ int size = delays.size() + (containsPlugged ? 0 : 1);
+ String[] delayStrings = new String[size];
double currentDelay = selectedDelay; // Store current setting locally
for (int i = 0; i < delays.size(); i++) {
- //// None
- delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), trans.get("TCMotorSelPan.delayBox.None"));
+ //// Plugged
+ delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), trans.get("TCMotorSelPan.delayBox.Plugged"));
+ }
+ // We always want the plugged option in the combobox, even if the motor doesn't have it
+ if (!containsPlugged) {
+ delayStrings[delayStrings.length - 1] = trans.get("TCMotorSelPan.delayBox.Plugged");
}
delayBox.setModel(new DefaultComboBoxModel(delayStrings));
if (reset) {
-
// Find and set the closest value
double closest = Double.NaN;
- for (int i = 0; i < delays.size(); i++) {
+ for (Double delay : delays) {
// if-condition to always become true for NaN
- if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
- closest = delays.get(i);
+ if (!(Math.abs(delay - currentDelay) > Math.abs(closest - currentDelay))) {
+ closest = delay;
}
}
if (!Double.isNaN(closest)) {
selectedDelay = closest;
- //// None
- delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, trans.get("TCMotorSelPan.delayBox.None")));
+ delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, trans.get("TCMotorSelPan.delayBox.Plugged")));
} else {
- delayBox.setSelectedItem("None");
+ //// Plugged
+ delayBox.setSelectedItem(trans.get("TCMotorSelPan.delayBox.Plugged"));
}
} else {
-
selectedDelay = currentDelay;
- //// None
- delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, trans.get("TCMotorSelPan.delayBox.None")));
-
+ delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, trans.get("TCMotorSelPan.delayBox.Plugged")));
}
}
diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
index 414344bb7..63b4de665 100644
--- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
+++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
@@ -465,7 +465,7 @@ public class BasicFrame extends JFrame {
fileMenu.add(item);
// export sim table...
- AbstractAction simTableExportAction = simulationPanel.getSimulationTableAsCSVExportAction();
+ AbstractAction simTableExportAction = simulationPanel.getExportSimulationTableAsCSVAction();
JMenuItem exportSimTableToCSVMenuItem = new JMenuItem(simTableExportAction);
fileMenu.add(exportSimTableToCSVMenuItem);
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index 5cc7ca8ce..78731f5e3 100644
--- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
@@ -114,6 +114,7 @@ public class SimulationPanel extends JPanel {
private final SimulationAction duplicateSimulationAction;
private final SimulationAction deleteSimulationAction;
private final SimulationAction simTableExportAction;
+ private final SimulationAction selectedSimsExportAction;
private int[] previousSelection = null;
private JMenuItem exportSimTableToCSVMenuItem;
@@ -132,6 +133,7 @@ public class SimulationPanel extends JPanel {
duplicateSimulationAction = new DuplicateSimulationAction();
deleteSimulationAction = new DeleteSimulationAction();
simTableExportAction = new ExportSimulationTableAsCSVAction();
+ selectedSimsExportAction = new ExportSelectedSimulationsAsCSVAction();
//////// The simulation action buttons ////////
@@ -190,7 +192,7 @@ public class SimulationPanel extends JPanel {
pm.addSeparator();
pm.add(runSimulationAction);
pm.add(plotSimulationAction);
- pm.add(simTableExportAction);
+ pm.add(selectedSimsExportAction);
// The normal left/right and tab/shift-tab key action traverses each cell/column of the table instead of going to the next row.
TableRowTraversalPolicy.setTableRowTraversalPolicy(simulationTable);
@@ -388,6 +390,80 @@ public class SimulationPanel extends JPanel {
openDialog(false, sims);
}
+ private void exportSimulationsToCSV(boolean onlySelected) {
+ Container tableParent = simulationTable.getParent();
+ int rowCount = simulationTableModel.getRowCount();
+
+ // I'm pretty sure with the enablement/disablement of the menu item under the File dropdown,
+ // that this would no longer be needed because if there is no sim table yet, the context menu
+ // won't show up. But I'm going to leave this in just in case....
+ if (rowCount <= 0) {
+ log.info("No simulation table rows to export");
+ JOptionPane.showMessageDialog(tableParent, trans.get("simpanel.dlg.no.simulation.table.rows"));
+ return;
+ }
+
+ JFileChooser fch = setUpSimExportCSVFileChooser();
+ int selectionStatus = fch.showSaveDialog(tableParent);
+ if (selectionStatus != JFileChooser.APPROVE_OPTION) {
+ log.debug("User cancelled CSV export");
+ return;
+ }
+
+ // Fetch the info from the file chooser
+ File CSVFile = fch.getSelectedFile();
+ CSVFile = FileHelper.forceExtension(CSVFile, "csv");
+ if (!FileHelper.confirmWrite(CSVFile, SimulationPanel.this)) {
+ log.debug("User cancelled CSV export overwrite");
+ return;
+ }
+
+ String separator = ((CsvOptionPanel) fch.getAccessory()).getFieldSeparator();
+ int precision = ((CsvOptionPanel) fch.getAccessory()).getDecimalPlaces();
+ boolean isExponentialNotation = ((CsvOptionPanel) fch.getAccessory()).isExponentialNotation();
+ ((CsvOptionPanel) fch.getAccessory()).storePreferences();
+
+ // Handle some special separator options from CsvOptionPanel
+ if (separator.equals(trans.get("CsvOptionPanel.separator.space"))) {
+ separator = " ";
+ } else if (separator.equals(trans.get("CsvOptionPanel.separator.tab"))) {
+ separator = "\t";
+ }
+
+ SimulationTableCSVExport exporter = new SimulationTableCSVExport(document, simulationTable, simulationTableModel);
+ exporter.export(CSVFile, separator, precision, isExponentialNotation, onlySelected);
+ }
+
+ /**
+ * Create the file chooser to save the CSV file.
+ * @return The file chooser.
+ */
+ private JFileChooser setUpSimExportCSVFileChooser() {
+ JFileChooser fch = new JFileChooser();
+ fch.setDialogTitle(trans.get("simpanel.pop.exportToCSV.save.dialog.title"));
+ fch.setFileFilter(FileHelper.CSV_FILTER);
+ fch.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
+ fch.setAcceptAllFileFilterUsed(false);
+
+ // Default output CSV to same name as the document's rocket name.
+ String fileName = document.getRocket().getName() + ".csv";
+ fch.setSelectedFile(new File(fileName));
+
+ // Add CSV options to FileChooser
+ CsvOptionPanel CSVOptions = new CsvOptionPanel(SimulationTableCSVExport.class);
+ fch.setAccessory(CSVOptions);
+
+ // TODO: update this dynamically instead of hard-coded values
+ // The macOS file chooser has an issue where it does not update its size when the accessory is added.
+ if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
+ Dimension currentSize = fch.getPreferredSize();
+ Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
+ fch.setPreferredSize(newSize);
+ }
+
+ return fch;
+ }
+
private Simulation[] getSelectedSimulations() {
int[] selection = simulationTable.getSelectedRows();
if (selection.length == 0) {
@@ -456,9 +532,9 @@ public class SimulationPanel extends JPanel {
/**
* Return the action for exporting the simulation table data to a CSV file.
- * @return
+ * @return the action for exporting the simulation table data to a CSV file.
*/
- public AbstractAction getSimulationTableAsCSVExportAction() {
+ public AbstractAction getExportSimulationTableAsCSVAction() {
return simTableExportAction;
}
@@ -473,6 +549,7 @@ public class SimulationPanel extends JPanel {
plotSimulationAction.updateEnabledState();
duplicateSimulationAction.updateEnabledState();
simTableExportAction.updateEnabledState();
+ selectedSimsExportAction.updateEnabledState();
}
/// when the simulation tab is selected this run outdated simulated if appropriate.
@@ -624,81 +701,19 @@ public class SimulationPanel extends JPanel {
}
}
+ /**
+ * Export the entire simulation table as a CSV file.
+ */
class ExportSimulationTableAsCSVAction extends SimulationAction {
public ExportSimulationTableAsCSVAction() {
- putValue(NAME, trans.get("simpanel.pop.exportToCSV"));
+ putValue(NAME, trans.get("simpanel.pop.exportSimTableToCSV"));
putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
}
@Override
public void actionPerformed(ActionEvent arg0) {
- Container tableParent = simulationTable.getParent();
- int rowCount = simulationTableModel.getRowCount();
-
- // I'm pretty sure with the enablement/disablement of the menu item under the File dropdown,
- // that this would no longer be needed because if there is no sim table yet, the context menu
- // won't show up. But I'm going to leave this in just in case....
- if (rowCount <= 0) {
- log.info("No simulation table rows to export");
- JOptionPane.showMessageDialog(tableParent, trans.get("simpanel.dlg.no.simulation.table.rows"));
- return;
- }
-
- JFileChooser fch = this.setUpFileChooser();
- int selectionStatus = fch.showSaveDialog(tableParent);
- if (selectionStatus != JFileChooser.APPROVE_OPTION) {
- log.info("User cancelled CSV export");
- return;
- }
-
- // Fetch the info from the file chooser
- File CSVFile = fch.getSelectedFile();
- CSVFile = FileHelper.forceExtension(CSVFile, "csv");
- String separator = ((CsvOptionPanel) fch.getAccessory()).getFieldSeparator();
- int precision = ((CsvOptionPanel) fch.getAccessory()).getDecimalPlaces();
- ((CsvOptionPanel) fch.getAccessory()).storePreferences();
-
- // Handle some special separator options from CsvOptionPanel
- if (separator.equals(trans.get("CsvOptionPanel.separator.space"))) {
- separator = " ";
- } else if (separator.equals(trans.get("CsvOptionPanel.separator.tab"))) {
- separator = "\t";
- }
-
- SimulationTableCSVExport exporter = new SimulationTableCSVExport(document, simulationTable, simulationTableModel);
- exporter.export(CSVFile, separator, precision);
- }
-
- /**
- * Create the file chooser to save the CSV file.
- * @return The file chooser.
- */
- private JFileChooser setUpFileChooser() {
- JFileChooser fch = new JFileChooser();
- fch.setDialogTitle(trans.get("simpanel.pop.exportToCSV.save.dialog.title"));
- fch.setFileFilter(FileHelper.CSV_FILTER);
- fch.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
- fch.setAcceptAllFileFilterUsed(false);
-
- // Default output CSV to same name as the document's rocket name.
- String fileName = document.getRocket().getName() + ".csv";
- fch.setSelectedFile(new File(fileName));
-
- // Add CSV options to FileChooser
- CsvOptionPanel CSVOptions = new CsvOptionPanel(SimulationTableCSVExport.class);
- fch.setAccessory(CSVOptions);
- fch.revalidate();
-
- // TODO: update this dynamically instead of hard-coded values
- // The macOS file chooser has an issue where it does not update its size when the accessory is added.
- if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
- Dimension currentSize = fch.getPreferredSize();
- Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
- fch.setPreferredSize(newSize);
- }
-
- return fch;
+ exportSimulationsToCSV(false);
}
@Override
@@ -708,6 +723,29 @@ public class SimulationPanel extends JPanel {
}
+ /**
+ * Export only the selected simulations as a CSV file.
+ */
+ class ExportSelectedSimulationsAsCSVAction extends SimulationAction {
+
+ public ExportSelectedSimulationsAsCSVAction() {
+ putValue(NAME, trans.get("simpanel.pop.exportSelectedSimsToCSV"));
+ putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ exportSimulationsToCSV(true);
+ }
+
+ @Override
+ public void updateEnabledState() {
+ setEnabled(simulationTableModel != null && simulationTableModel.getRowCount() > 0 &&
+ simulationTable.getSelectedRowCount() > 0);
+ }
+
+ }
+
class PlotSimulationAction extends SimulationAction {
public PlotSimulationAction() {
putValue(NAME, trans.get("simpanel.pop.plot"));
diff --git a/swing/src/net/sf/openrocket/gui/plot/EventGraphics.java b/swing/src/net/sf/openrocket/gui/plot/EventGraphics.java
index 3443e9be5..6dd647b2b 100644
--- a/swing/src/net/sf/openrocket/gui/plot/EventGraphics.java
+++ b/swing/src/net/sf/openrocket/gui/plot/EventGraphics.java
@@ -57,6 +57,7 @@ public class EventGraphics {
"pix/eventicons/event-recovery-device-deployment.png");
loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
+ loadImage(FlightEvent.Type.EXCEPTION, "pix/eventicons/event-exception.png");
}
private static void loadImage(FlightEvent.Type type, String file) {
diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java
index 63ac79c29..2ff3c7101 100644
--- a/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java
+++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java
@@ -38,6 +38,7 @@ import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.IgnitionEvent;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
+import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
@@ -433,12 +434,20 @@ public class SimulationRunDialog extends JDialog {
null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
} else if (t instanceof SimulationException) {
+ String title = simulation.getName();
+ FlightDataBranch dataBranch = ((SimulationException) t).getFlightDataBranch();
+ String message;
+ if (dataBranch != null) {
+ message = trans.get("SimuRunDlg.msg.branchErrorOccurred") + "\"" + dataBranch.getBranchName() + "\"";
+ } else {
+ message = trans.get("SimuRunDlg.msg.errorOccurred");
+ }
DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
new Object[] {
//// A error occurred during the simulation:
- trans.get("SimuRunDlg.msg.errorOccurred"), t.getMessage() },
- null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
+ message, t.getMessage() },
+ null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
} else {