Merge branch 'unstable' into issue-875

This commit is contained in:
SiboVG 2023-03-20 12:41:12 +01:00
commit 2dc4a0fd39
41 changed files with 679 additions and 259 deletions

View File

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

View File

@ -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 = مسح الكل

View File

@ -940,8 +940,7 @@ TCMotorSelPan.lbl.Motormountdia = Prumer uchycen
TCMotorSelPan.lbl.Search = Hledej:
TCMotorSelPan.lbl.Selectthrustcurve = Vyber výkonovou 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 = Pojmenování:
TCMotorSelPan.lbl.Totalimpulse = Celkový impulse:
TCMotorSelPan.lbl.Avgthrust = Prumerný tah:
@ -953,7 +952,7 @@ TCMotorSelPan.lbl.Datapoints = Datov
TCMotorSelPan.lbl.Digest = Výber:
TCMotorSelPan.title.Thrustcurve = Výkonová krivka:
TCMotorSelPan.title.Thrust = Tah
TCMotorSelPan.delayBox.None = Nic
TCMotorSelPan.delayBox.Plugged = Nic
! PlotDialog

View File

@ -998,8 +998,7 @@ TCMotorSelPan.lbl.Motormountdia = Durchmesser der Motorhalterung
TCMotorSelPan.lbl.Search = Suchen:
TCMotorSelPan.lbl.Selectthrustcurve = Schubkurve auswählen:
TCMotorSelPan.lbl.Ejectionchargedelay = Verzögerung der Ausstoßladung:
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -943,8 +943,7 @@ ComponentInfo.EngineBlock = <b>Blokada silnika</b> 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 = <b>Blokada silnika</b> 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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

View File

@ -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 <code>Warning</code> 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 <code>Warning</code> 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"));

View File

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

View File

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

View File

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

View File

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

View File

@ -109,6 +109,13 @@ class SingleSimulationHandler extends AbstractElementHandler {
public void endHandler(String element, HashMap<String, String> 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,

View File

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

View File

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

View File

@ -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<Double>());
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<FlightEvent> eventList = branch.getEvents();
final List<FlightEvent.Type> 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());
}
}
}
}

View File

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

View File

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

View File

@ -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<String> rowColumnElement = new ArrayList<>();
for (int j = 1; j<modelColumnCount ; j++) {
for (int j = 1; j < modelColumnCount ; j++) {
String colName = simulationTable.getColumnName(j);
// Get the unit string and append to column that it applies to. Columns w/o units will remain unchanged.
@ -114,10 +116,24 @@ public class SimulationTableCSVExport {
StringBuilder fullOutputResult = new StringBuilder(CSVSimResultString);
// Get relevant data and create the comma separated data from it.
for (int i = 0; i < modelRowCount; i++) {
int[] iterator;
if (onlySelected) {
iterator = simulationTable.getSelectedRows();
} else {
iterator = new int[modelRowCount];
for (int i = 0; i < modelRowCount; i++) {
iterator[i] = i;
}
}
for (int i : iterator) {
// Account for sorting... resulting CSV file will be in the same order as shown in the table thanks to this gem.
int idx = simulationTable.convertRowIndexToModel(i);
// Ignore empty simulation
if (!document.getSimulation(idx).hasSummaryData()) {
continue;
}
int nullCnt = 0;
rowColumnElement.clear();
@ -139,7 +155,7 @@ public class SimulationTableCSVExport {
final String valueString;
if (o instanceof Value) {
double value = ((Value) o).getUnitValue();
valueString = TextUtil.doubleToString(value, precision);
valueString = TextUtil.doubleToString(value, precision, isExponentialNotation);
} else {
valueString = o.toString();
}
@ -168,13 +184,21 @@ public class SimulationTableCSVExport {
return fullOutputResult.toString();
}
public void export(File file, String fieldSep, int precision) {
/**
* Export the simulation table data to a CSV file
* @param file the file to save the results to
* @param fieldSep the CSV separator to use
* @param precision the decimal precision to use in the CSV file
* @param isExponentialNotation if true, use exponential notation for numbers
* @param onlySelected if true, only export the selected rows in the table
*/
public void export(File file, String fieldSep, int precision, boolean isExponentialNotation, boolean onlySelected) {
if (file == null) {
log.warn("No file selected for export");
return;
}
String CSVData = generateCSVDate(fieldSep, precision);
String CSVData = generateCSVDate(fieldSep, precision, isExponentialNotation, onlySelected);
this.dumpDataToFile(CSVData, file);
log.info("Simulation table data exported to " + file.getAbsolutePath());
}

View File

@ -99,6 +99,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
private final JLabel curveSelectionLabel;
private final JComboBox<MotorHolder> curveSelectionBox;
private final DefaultComboBoxModel<MotorHolder> curveSelectionModel;
private final JLabel ejectionChargeDelayLabel;
private final JComboBox<String> 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<String>();
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<ThrustCurveMotor> 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<String>(new String[] { trans.get("TCMotorSelPan.delayBox.None") }));
delayBox.setSelectedIndex(0);
//// Display nothing
delayBox.setModel(new DefaultComboBoxModel<>(new String[] {}));
} else {
List<Double> 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<String>(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")));
}
}

View File

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

View File

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

View File

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

View File

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