Merge branch 'unstable' into issue-875
This commit is contained in:
commit
2dc4a0fd39
@ -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.
|
||||
|
||||
@ -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 = مسح الكل
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
BIN
core/resources/pix/eventicons/event-exception.png
Normal file
BIN
core/resources/pix/eventicons/event-exception.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 700 B |
@ -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"));
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()));
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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());
|
||||
}
|
||||
|
||||
@ -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")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user