Merge branch 'unstable' into no-tumble-warning

This commit is contained in:
JoePfeiffer 2024-10-15 17:23:26 -06:00
commit edf4d03322
24 changed files with 452 additions and 154 deletions

View File

@ -22,6 +22,7 @@ import info.openrocket.core.util.ModID;
* @author Sampo Niskanen <sampo.niskanen@iki.fi> * @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/ */
public class FlightConditions implements Cloneable, ChangeSource, Monitorable { public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
private static final double MIN_BETA = 0.25;
private List<EventListener> listenerList = new ArrayList<>(); private List<EventListener> listenerList = new ArrayList<>();
private EventObject event = new EventObject(this); private EventObject event = new EventObject(this);
@ -55,7 +56,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
* Sqrt(1 - M^2) for M<1 * Sqrt(1 - M^2) for M<1
* Sqrt(M^2 - 1) for M>1 * Sqrt(M^2 - 1) for M>1
*/ */
private double beta = MathUtil.safeSqrt(1 - mach * mach); private double beta = calculateBeta(mach);
/** Current roll rate. */ /** Current roll rate. */
private double rollRate = 0; private double rollRate = 0;
@ -243,10 +244,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
return; return;
this.mach = mach; this.mach = mach;
if (mach < 1) this.beta = calculateBeta(mach);
this.beta = MathUtil.safeSqrt(1 - mach * mach);
else
this.beta = MathUtil.safeSqrt(mach * mach - 1);
fireChangeEvent(); fireChangeEvent();
} }
@ -288,6 +286,19 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
return beta; return beta;
} }
/**
* Calculate the beta value (compressibility factor/Prandtl-Glauert correction factor) for the given Mach number.
* @param mach the Mach number.
* @return the beta value.
*/
private static double calculateBeta(double mach) {
if (mach < 1) {
return MathUtil.max(MIN_BETA, MathUtil.safeSqrt(1 - mach * mach));
} else {
return MathUtil.max(MIN_BETA, MathUtil.safeSqrt(mach * mach - 1));
}
}
/** /**
* @return the current roll rate. * @return the current roll rate.
*/ */

View File

@ -162,9 +162,6 @@ public class FinSetCalc extends RocketComponentCalc {
// TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25 // TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25
// Calculate CP position // Calculate CP position
double x = macLead + calculateCPPos(conditions) * macLength; double x = macLead + calculateCPPos(conditions) * macLength;
// logger.debug("Component macLead = {}", macLead);
// logger.debug("Component macLength = {}", macLength);
// logger.debug("Component x = {}", x);
// Calculate roll forces, reduce forcing above stall angle // Calculate roll forces, reduce forcing above stall angle
@ -176,17 +173,12 @@ public class FinSetCalc extends RocketComponentCalc {
forces.setCrollForce((macSpan + r) * cna1 * (1 + tau) * cantAngle / conditions.getRefLength()); forces.setCrollForce((macSpan + r) * cna1 * (1 + tau) * cantAngle / conditions.getRefLength());
if (conditions.getAOA() > STALL_ANGLE) { if (conditions.getAOA() > STALL_ANGLE) {
// System.out.println("Fin stalling in roll");
forces.setCrollForce(forces.getCrollForce() * MathUtil.clamp( forces.setCrollForce(forces.getCrollForce() * MathUtil.clamp(
1 - (conditions.getAOA() - STALL_ANGLE) / (STALL_ANGLE / 2), 0, 1)); 1 - (conditions.getAOA() - STALL_ANGLE) / (STALL_ANGLE / 2), 0, 1));
} }
forces.setCrollDamp(calculateDampingMoment(conditions)); forces.setCrollDamp(calculateDampingMoment(conditions));
forces.setCroll(forces.getCrollForce() - forces.getCrollDamp()); forces.setCroll(forces.getCrollForce() - forces.getCrollDamp());
// System.out.printf(component.getName() + ": roll rate:%.3f force:%.3f damp:%.3f " +
// "total:%.3f\n",
// conditions.getRollRate(), forces.CrollForce, forces.CrollDamp, forces.Croll);
forces.setCNa(cna); forces.setCNa(cna);
forces.setCN(cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE)); forces.setCN(cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE));
forces.setCP(new Coordinate(x, 0, 0, cna)); forces.setCP(new Coordinate(x, 0, 0, cna));
@ -349,7 +341,6 @@ public class FinSetCalc extends RocketComponentCalc {
double y = i * dy; double y = i * dy;
macLength += length * length; macLength += length * length;
//logger.debug("macLength = {}, length = {}, i = {}", macLength, length, i);
macSpan += y * length; macSpan += y * length;
macLead += chordLead[i] * length; macLead += chordLead[i] * length;
area += length; area += length;
@ -408,10 +399,6 @@ public class FinSetCalc extends RocketComponentCalc {
K1 = new LinearInterpolator(x, k1); K1 = new LinearInterpolator(x, k1);
K2 = new LinearInterpolator(x, k2); K2 = new LinearInterpolator(x, k2);
K3 = new LinearInterpolator(x, k3); K3 = new LinearInterpolator(x, k3);
// System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
// System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
// System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
} }
protected double calculateFinCNa1(FlightConditions conditions) { protected double calculateFinCNa1(FlightConditions conditions) {
@ -445,8 +432,6 @@ public class FinSetCalc extends RocketComponentCalc {
K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref; K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref;
superD = -finArea / ref * 2 * CNA_SUPERSONIC / CNA_SUPERSONIC_B; superD = -finArea / ref * 2 * CNA_SUPERSONIC / CNA_SUPERSONIC_B;
// System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
return cnaInterpolator.interpolate(mach, subV, superV, subD, superD, 0); return cnaInterpolator.interpolate(mach, subV, superV, subD, superD, 0);
} }
@ -474,23 +459,15 @@ public class FinSetCalc extends RocketComponentCalc {
sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() / sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() /
(conditions.getRefArea() * conditions.getRefLength()); (conditions.getRefArea() * conditions.getRefLength());
// System.out.println("SPECIAL: " +
// (MathUtil.sign(rollRate) * sum));
return MathUtil.sign(rollRate) * sum; return MathUtil.sign(rollRate) * sum;
} }
if (mach <= CNA_SUBSONIC) { if (mach <= CNA_SUBSONIC) {
// System.out.println("BASIC: "+
// (2*Math.PI * rollRate * rollSum /
// (conditions.getRefArea() * conditions.getRefLength() *
// conditions.getVelocity() * conditions.getBeta())));
return 2 * Math.PI * rollRate * rollSum / return 2 * Math.PI * rollRate * rollSum /
(conditions.getRefArea() * conditions.getRefLength() * (conditions.getRefArea() * conditions.getRefLength() *
conditions.getVelocity() * conditions.getBeta()); conditions.getVelocity() * conditions.getBeta());
} }
if (mach >= CNA_SUPERSONIC) { if (mach >= CNA_SUPERSONIC) {
double vel = conditions.getVelocity(); double vel = conditions.getVelocity();
double k1 = K1.getValue(mach); double k1 = K1.getValue(mach);
double k2 = K2.getValue(mach); double k2 = K2.getValue(mach);
@ -511,7 +488,6 @@ public class FinSetCalc extends RocketComponentCalc {
} }
// Transonic, do linear interpolation // Transonic, do linear interpolation
FlightConditions cond = conditions.clone(); FlightConditions cond = conditions.clone();
cond.setMach(CNA_SUBSONIC - 0.01); cond.setMach(CNA_SUBSONIC - 0.01);
double subsonic = calculateDampingMoment(cond); double subsonic = calculateDampingMoment(cond);
@ -532,7 +508,7 @@ public class FinSetCalc extends RocketComponentCalc {
*/ */
private double calculateCPPos(FlightConditions cond) { private double calculateCPPos(FlightConditions cond) {
double m = cond.getMach(); double m = cond.getMach();
// logger.debug("m = {} ", m);
if (m <= 0.5) { if (m <= 0.5) {
// At subsonic speeds CP at quarter chord // At subsonic speeds CP at quarter chord
return 0.25; return 0.25;
@ -551,7 +527,7 @@ public class FinSetCalc extends RocketComponentCalc {
val += v * x; val += v * x;
x *= m; x *= m;
} }
// logger.debug("val = {}", val);
return val; return val;
} }

View File

@ -1342,10 +1342,10 @@ FreeformFinSetCfg.lbl.Fincant = Fin cant:
FreeformFinSetCfg.lbl.ttip.Fincant = The angle that the fins are canted with respect to the rocket body. FreeformFinSetCfg.lbl.ttip.Fincant = The angle that the fins are canted with respect to the rocket body.
FreeformFinSetCfg.lbl.FincrossSection = Fin cross section: FreeformFinSetCfg.lbl.FincrossSection = Fin cross section:
FreeformFinSetCfg.lbl.Thickness = Thickness: FreeformFinSetCfg.lbl.Thickness = Thickness:
FreeformFinSetConfig.lbl.doubleClick1 = Double-click
FreeformFinSetConfig.lbl.doubleClick2 = to edit
FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Delete point FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Delete point
FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
FreeformFinSetConfig.lbl.shiftClickDrag = Shift+click+drag: Lock angle to previous point
FreeformFinSetConfig.lbl.ctrlShiftClickDrag = Ctrl+shift+click+drag: Lock angle to following point
FreeformFinSetConfig.lbl.scaleFin = Scale Fin FreeformFinSetConfig.lbl.scaleFin = Scale Fin
FreeformFinSetConfig.lbl.exportCSV = Export CSV FreeformFinSetConfig.lbl.exportCSV = Export CSV
FreeformFinSetConfig.lbl.deletePoint = Delete point FreeformFinSetConfig.lbl.deletePoint = Delete point

View File

@ -1033,9 +1033,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = :الموقع بالنسبة إلى
FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.plus = plus
FreeformFinSetCfg.lbl.FincrossSection = :المقطع العرضي للزعنفة FreeformFinSetCfg.lbl.FincrossSection = :المقطع العرضي للزعنفة
FreeformFinSetCfg.lbl.Thickness = :السماكة FreeformFinSetCfg.lbl.Thickness = :السماكة
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = نقرتين
FreeformFinSetConfig.lbl.doubleClick2 = للتعديل
FreeformFinSetConfig.lbl.clickDrag = أنقر وإسحب: لإضافة وتحريك نقاط FreeformFinSetConfig.lbl.clickDrag = أنقر وإسحب: لإضافة وتحريك نقاط
FreeformFinSetConfig.lbl.ctrlClick = مفتاح التحكم مع النقر: لحذف نقطة FreeformFinSetConfig.lbl.ctrlClick = مفتاح التحكم مع النقر: لحذف نقطة
FreeformFinSetConfig.lbl.scaleFin = حَجِّمْ الزعنفة FreeformFinSetConfig.lbl.scaleFin = حَجِّمْ الزعنفة

View File

@ -715,9 +715,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Pozice vzhledem k:
FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.plus = plus
FreeformFinSetCfg.lbl.FincrossSection = Hrany stabilizátoru: FreeformFinSetCfg.lbl.FincrossSection = Hrany stabilizátoru:
FreeformFinSetCfg.lbl.Thickness = Tlou\u0161tka: FreeformFinSetCfg.lbl.Thickness = Tlou\u0161tka:
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Dvoj klik
FreeformFinSetConfig.lbl.doubleClick2 = k editaci
FreeformFinSetConfig.lbl.clickDrag = Klik a táhnout: Pridej a presun body FreeformFinSetConfig.lbl.clickDrag = Klik a táhnout: Pridej a presun body
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Odstran bod FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Odstran bod
FreeformFinSetConfig.lbl.scaleFin = Merítko stabilizátoru FreeformFinSetConfig.lbl.scaleFin = Merítko stabilizátoru

View File

@ -772,9 +772,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Position relativ zu:
FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.plus = plus
FreeformFinSetCfg.lbl.FincrossSection = Querschnitt: FreeformFinSetCfg.lbl.FincrossSection = Querschnitt:
FreeformFinSetCfg.lbl.Thickness = Wandstärke: FreeformFinSetCfg.lbl.Thickness = Wandstärke:
! doubleClick1 + 2 form the message "Doppelklick zum Bearbeiten", ungefähr in der Mitte teilen
FreeformFinSetConfig.lbl.doubleClick1 = Doppelklick
FreeformFinSetConfig.lbl.doubleClick2 = zum Bearbeiten
FreeformFinSetConfig.lbl.clickDrag = Klicken+Ziehen: Punkte bewegen und hinzufügen FreeformFinSetConfig.lbl.clickDrag = Klicken+Ziehen: Punkte bewegen und hinzufügen
FreeformFinSetConfig.lbl.ctrlClick = Strg+Klick: Punkt löschen FreeformFinSetConfig.lbl.ctrlClick = Strg+Klick: Punkt löschen
FreeformFinSetConfig.lbl.scaleFin = Leitwerk skalieren FreeformFinSetConfig.lbl.scaleFin = Leitwerk skalieren

View File

@ -374,9 +374,6 @@ FreeformFinSetCfg.tab.ttip.General = Propiedades generales
FreeformFinSetConfig.lbl.clickDrag = Click (sobre l\u00ednea)+arrastrar: Agregar punto FreeformFinSetConfig.lbl.clickDrag = Click (sobre l\u00ednea)+arrastrar: Agregar punto
FreeformFinSetConfig.lbl.ctrlClick = Control+Click (sobre punto): Eliminar punto FreeformFinSetConfig.lbl.ctrlClick = Control+Click (sobre punto): Eliminar punto
!DobleClic 1 + 2 en el mensaje "Doble-Click para editar", corta aproximadamente por la mitad
FreeformFinSetConfig.lbl.doubleClick1 = Doble Click en la lista
FreeformFinSetConfig.lbl.doubleClick2 = para editar
FreeformFinSetConfig.lbl.scaleFin = Dimensionar FreeformFinSetConfig.lbl.scaleFin = Dimensionar
GeneralOptimizationDialog.basicSimulationName = Simulaci\u00f3n b\u00e1sica GeneralOptimizationDialog.basicSimulationName = Simulaci\u00f3n b\u00e1sica

View File

@ -365,9 +365,6 @@ FreeformFinSetCfg.tab.ttip.General = Propri\u00E9t\u00E9s g\u00E9n\u00E9rales
FreeformFinSetConfig.lbl.clickDrag = Cliquer+d\u00E9placer: Ajouter et d\u00E9placer des points FreeformFinSetConfig.lbl.clickDrag = Cliquer+d\u00E9placer: Ajouter et d\u00E9placer des points
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+cliquer: Enlever un point FreeformFinSetConfig.lbl.ctrlClick = Ctrl+cliquer: Enlever un point
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Double-cliquer
FreeformFinSetConfig.lbl.doubleClick2 = pour modifier
FreeformFinSetConfig.lbl.scaleFin = Redimensionner les ailerons FreeformFinSetConfig.lbl.scaleFin = Redimensionner les ailerons
GeneralOptimizationDialog.basicSimulationName = Simulation simple GeneralOptimizationDialog.basicSimulationName = Simulation simple

View File

@ -774,9 +774,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Posizione relativa a :
FreeformFinSetCfg.lbl.plus = pi\u00f9 FreeformFinSetCfg.lbl.plus = pi\u00f9
FreeformFinSetCfg.lbl.FincrossSection = Sezione delle pinne FreeformFinSetCfg.lbl.FincrossSection = Sezione delle pinne
FreeformFinSetCfg.lbl.Thickness = Spessore: FreeformFinSetCfg.lbl.Thickness = Spessore:
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Doppio-click
FreeformFinSetConfig.lbl.doubleClick2 = per modificare
FreeformFinSetConfig.lbl.clickDrag = Click+muovi: aggiunge e muove punti FreeformFinSetConfig.lbl.clickDrag = Click+muovi: aggiunge e muove punti
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: rimuove punti FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: rimuove punti
FreeformFinSetConfig.lbl.scaleFin = Scala pinna FreeformFinSetConfig.lbl.scaleFin = Scala pinna

View File

@ -804,9 +804,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = \u4F4D\u7F6E\uFF1A
FreeformFinSetCfg.lbl.plus = \u30D7\u30E9\u30B9 FreeformFinSetCfg.lbl.plus = \u30D7\u30E9\u30B9
FreeformFinSetCfg.lbl.FincrossSection = \u30D5\u30A3\u30F3\u65AD\u9762\u7A4D\uFF1A FreeformFinSetCfg.lbl.FincrossSection = \u30D5\u30A3\u30F3\u65AD\u9762\u7A4D\uFF1A
FreeformFinSetCfg.lbl.Thickness = \u539A\u3055\uFF1A FreeformFinSetCfg.lbl.Thickness = \u539A\u3055\uFF1A
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = \u30C0\u30D6\u30EB\u30AF\u30EA\u30C3\u30AF\u3067
FreeformFinSetConfig.lbl.doubleClick2 = \u7DE8\u96C6
FreeformFinSetConfig.lbl.clickDrag = Click+drag: \u30DD\u30A4\u30F3\u30C8\u306E\u8FFD\u52A0\u3068\u79FB\u52D5 FreeformFinSetConfig.lbl.clickDrag = Click+drag: \u30DD\u30A4\u30F3\u30C8\u306E\u8FFD\u52A0\u3068\u79FB\u52D5
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: \u30DD\u30A4\u30F3\u30C8\u306E\u524A\u9664 FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: \u30DD\u30A4\u30F3\u30C8\u306E\u524A\u9664
FreeformFinSetConfig.lbl.scaleFin = Scale Fin FreeformFinSetConfig.lbl.scaleFin = Scale Fin

View File

@ -984,9 +984,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Positie relatief t.o.v.:
FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.plus = plus
FreeformFinSetCfg.lbl.FincrossSection = Vindoorsnede: FreeformFinSetCfg.lbl.FincrossSection = Vindoorsnede:
FreeformFinSetCfg.lbl.Thickness = Dikte: FreeformFinSetCfg.lbl.Thickness = Dikte:
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Dubbelklik op
FreeformFinSetConfig.lbl.doubleClick2 = om te bewerken
FreeformFinSetConfig.lbl.clickDrag = Klik+sleep: Punten toevoegen en verplaatsen FreeformFinSetConfig.lbl.clickDrag = Klik+sleep: Punten toevoegen en verplaatsen
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Verwijder punt FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Verwijder punt
FreeformFinSetConfig.lbl.scaleFin = Schaal vin FreeformFinSetConfig.lbl.scaleFin = Schaal vin

View File

@ -719,9 +719,6 @@ ComponentInfo.EngineBlock = <b>Blokada silnika</b> unieruchamia silnik wewn\u01
FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.plus = plus
FreeformFinSetCfg.lbl.FincrossSection = Przekrój statecznika: FreeformFinSetCfg.lbl.FincrossSection = Przekrój statecznika:
FreeformFinSetCfg.lbl.Thickness = Grubo\u015B\u0107: FreeformFinSetCfg.lbl.Thickness = Grubo\u015B\u0107:
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Kliknij dwukrotnie
FreeformFinSetConfig.lbl.doubleClick2 = aby edytowa\u0107
FreeformFinSetConfig.lbl.clickDrag = Kliknij i przeci\u0105gnij: Dodaj i przesuwaj punkty FreeformFinSetConfig.lbl.clickDrag = Kliknij i przeci\u0105gnij: Dodaj i przesuwaj punkty
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Usu\u0144 punkt FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Usu\u0144 punkt
FreeformFinSetConfig.lbl.scaleFin = Skaluj statecznik FreeformFinSetConfig.lbl.scaleFin = Skaluj statecznik

View File

@ -354,9 +354,6 @@ FreeformFinSetCfg.tab.ttip.General = Propriedades gerais
FreeformFinSetConfig.lbl.clickDrag = Clique+arraste: Adicionar e mover pontos FreeformFinSetConfig.lbl.clickDrag = Clique+arraste: Adicionar e mover pontos
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+clique em: Remover ponto FreeformFinSetConfig.lbl.ctrlClick = Ctrl+clique em: Remover ponto
# doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = Duplo clique
FreeformFinSetConfig.lbl.doubleClick2 = editar
FreeformFinSetConfig.lbl.scaleFin = Escala da aleta FreeformFinSetConfig.lbl.scaleFin = Escala da aleta
GeneralOptimizationDialog.basicSimulationName = Simula\u00e7\u00e3o b\u00e1sica GeneralOptimizationDialog.basicSimulationName = Simula\u00e7\u00e3o b\u00e1sica

View File

@ -1013,9 +1013,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = \u041F\u043E\u043B\u043E\u0436\u0435\u043D
FreeformFinSetCfg.lbl.plus = \u043F\u043B\u044E\u0441 FreeformFinSetCfg.lbl.plus = \u043F\u043B\u044E\u0441
FreeformFinSetCfg.lbl.FincrossSection = \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0430\u0442\u043E\u0440\u0430: FreeformFinSetCfg.lbl.FincrossSection = \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0430\u0442\u043E\u0440\u0430:
FreeformFinSetCfg.lbl.Thickness = \u0422\u043E\u043B\u0449\u0438\u043D\u0430: FreeformFinSetCfg.lbl.Thickness = \u0422\u043E\u043B\u0449\u0438\u043D\u0430:
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = \u0414\u0432\u043E\u0439\u043D\u043E\u0439 \u043A\u043B\u0438\u043A
FreeformFinSetConfig.lbl.doubleClick2 = \u0434\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F
FreeformFinSetConfig.lbl.clickDrag = \u041A\u043B\u0438\u043A+\u0442\u0430\u0449\u0438\u0442\u044C: \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0438 \u0434\u0432\u0438\u0433\u0430\u0442\u044C \u0442\u043E\u0447\u043A\u0438 FreeformFinSetConfig.lbl.clickDrag = \u041A\u043B\u0438\u043A+\u0442\u0430\u0449\u0438\u0442\u044C: \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0438 \u0434\u0432\u0438\u0433\u0430\u0442\u044C \u0442\u043E\u0447\u043A\u0438
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u043A\u043B\u0438\u043A: \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u043E\u0447\u043A\u0443 FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u043A\u043B\u0438\u043A: \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u043E\u0447\u043A\u0443
FreeformFinSetConfig.lbl.scaleFin = \u041C\u0430\u0441\u0448\u0442\u0430\u0431 \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0430\u0442\u043E\u0440\u0430 FreeformFinSetConfig.lbl.scaleFin = \u041C\u0430\u0441\u0448\u0442\u0430\u0431 \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0430\u0442\u043E\u0440\u0430

View File

@ -1225,8 +1225,6 @@ FreeformFinSetCfg.lbl.Fincant = \u041d\u0430\u0445\u0438\u043b \u043a\u0456\u043
FreeformFinSetCfg.lbl.ttip.Fincant = \u041a\u0443\u0442, \u043f\u0456\u0434 \u044f\u043a\u0438\u043c \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043d\u0430\u0445\u0438\u043b\u0435\u043d\u043e \u0432\u0456\u0434\u043d\u043e\u0441\u043d\u043e \u043a\u043e\u0440\u043f\u0443\u0441\u0443 \u0440\u0430\u043a\u0435\u0442\u0438. FreeformFinSetCfg.lbl.ttip.Fincant = \u041a\u0443\u0442, \u043f\u0456\u0434 \u044f\u043a\u0438\u043c \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043d\u0430\u0445\u0438\u043b\u0435\u043d\u043e \u0432\u0456\u0434\u043d\u043e\u0441\u043d\u043e \u043a\u043e\u0440\u043f\u0443\u0441\u0443 \u0440\u0430\u043a\u0435\u0442\u0438.
FreeformFinSetCfg.lbl.FincrossSection = \u041f\u0435\u0440\u0435\u0440\u0456\u0437 \u043a\u0456\u043b\u044c\u043a\u043e\u0441\u0442\u0456: FreeformFinSetCfg.lbl.FincrossSection = \u041f\u0435\u0440\u0435\u0440\u0456\u0437 \u043a\u0456\u043b\u044c\u043a\u043e\u0441\u0442\u0456:
FreeformFinSetCfg.lbl.Thickness = \u0422\u043e\u0432\u0449\u0438\u043d\u0430: FreeformFinSetCfg.lbl.Thickness = \u0422\u043e\u0432\u0449\u0438\u043d\u0430:
FreeformFinSetConfig.lbl.doubleClick1 = \u0414\u0432\u0456\u0447\u0456 \u043a\u043b\u0456\u043a\u043d\u0456\u0442\u044c
FreeformFinSetConfig.lbl.doubleClick2 = \u0449\u043e\u0431 \u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438
FreeformFinSetConfig.lbl.clickDrag = \u041a\u043b\u0456\u043a+\u043f\u0435\u0440\u0435\u0442\u044f\u0433\u0443\u0432\u0430\u043d\u043d\u044f: \u0414\u043e\u0434\u0430\u0442\u0438 \u0442\u0430 \u043f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 FreeformFinSetConfig.lbl.clickDrag = \u041a\u043b\u0456\u043a+\u043f\u0435\u0440\u0435\u0442\u044f\u0433\u0443\u0432\u0430\u043d\u043d\u044f: \u0414\u043e\u0434\u0430\u0442\u0438 \u0442\u0430 \u043f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u043a\u043b\u0456\u043a: \u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u043a\u043b\u0456\u043a: \u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443
FreeformFinSetConfig.lbl.scaleFin = \u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0443\u0432\u0430\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c FreeformFinSetConfig.lbl.scaleFin = \u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0443\u0432\u0430\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c

View File

@ -391,9 +391,6 @@ FreeformFinSetCfg.tab.ttip.General = \u5E38\u89C4\u5C5E\u6027
FreeformFinSetConfig.lbl.clickDrag = \u5355\u51FB+\u62D6\u62FD: \u6DFB\u52A0,\u79FB\u52A8\u70B9 FreeformFinSetConfig.lbl.clickDrag = \u5355\u51FB+\u62D6\u62FD: \u6DFB\u52A0,\u79FB\u52A8\u70B9
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u5355\u51FB: \u5220\u9664\u70B9 FreeformFinSetConfig.lbl.ctrlClick = Ctrl+\u5355\u51FB: \u5220\u9664\u70B9
! doubleClick1 + 2 form the message "Double-click to edit", split approximately at the middle
FreeformFinSetConfig.lbl.doubleClick1 = \u53CC\u51FB
FreeformFinSetConfig.lbl.doubleClick2 = \u7F16\u8F91
FreeformFinSetConfig.lbl.scaleFin = \u7F29\u653E\u7A33\u5B9A\u7FFC FreeformFinSetConfig.lbl.scaleFin = \u7F29\u653E\u7A33\u5B9A\u7FFC
GeneralOptimizationDialog.basicSimulationName = \u57FA\u672C\u4EFF\u771F GeneralOptimizationDialog.basicSimulationName = \u57FA\u672C\u4EFF\u771F

View File

@ -0,0 +1,213 @@
package info.openrocket.core.aerodynamics;
import static org.junit.jupiter.api.Assertions.*;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import info.openrocket.core.ServicesForTesting;
import info.openrocket.core.document.OpenRocketDocument;
import info.openrocket.core.document.OpenRocketDocumentFactory;
import info.openrocket.core.plugin.PluginModule;
import info.openrocket.core.rocketcomponent.BodyTube;
import info.openrocket.core.rocketcomponent.Rocket;
import info.openrocket.core.startup.Application;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import info.openrocket.core.models.atmosphere.AtmosphericConditions;
import info.openrocket.core.rocketcomponent.FlightConfiguration;
import info.openrocket.core.util.Coordinate;
class FlightConditionsTest {
private FlightConditions conditions;
private static final double EPSILON = 1e-6;
private Rocket rocket;
@BeforeEach
void setUp() {
com.google.inject.Module applicationModule = new ServicesForTesting();
Module pluginModule = new PluginModule();
Injector injector = Guice.createInjector(applicationModule, pluginModule);
Application.setInjector(injector);
OpenRocketDocument document = OpenRocketDocumentFactory.createNewRocket();
this.rocket = document.getRocket();
BodyTube bodyTube = new BodyTube();
bodyTube.setLength(1.0);
bodyTube.setOuterRadius(0.05);
this.rocket.getChild(0).addChild(bodyTube);
FlightConfiguration mockConfig = new FlightConfiguration(rocket);
this.conditions = new FlightConditions(mockConfig);
}
@Test
void testSetAndGetRefLength() {
double expectedLength = ((BodyTube) rocket.getChild(0).getChild(0)).getOuterRadius() * 2;
assertEquals(expectedLength, conditions.getRefLength(), EPSILON);
conditions.setRefLength(2.0);
assertEquals(2.0, conditions.getRefLength(), EPSILON);
assertEquals(Math.PI, conditions.getRefArea(), EPSILON);
}
@Test
void testSetAndGetRefArea() {
// Get the actual reference area from FlightConditions
double actualRefArea = conditions.getRefArea();
// Test that setting this area results in the same value
conditions.setRefArea(actualRefArea);
assertEquals(actualRefArea, conditions.getRefArea(), EPSILON);
// Test that setting a new area works correctly
double newArea = 4.0;
conditions.setRefArea(newArea);
assertEquals(newArea, conditions.getRefArea(), EPSILON);
assertEquals(Math.sqrt(newArea / Math.PI) * 2, conditions.getRefLength(), EPSILON);
}
@Test
void testSetAndGetAOA() {
conditions.setAOA(Math.PI / 4);
assertEquals(Math.PI / 4, conditions.getAOA(), EPSILON);
assertEquals(Math.sin(Math.PI / 4), conditions.getSinAOA(), EPSILON);
assertEquals(Math.sin(Math.PI / 4) / (Math.PI / 4), conditions.getSincAOA(), EPSILON);
}
@Test
void testSetAndGetTheta() {
conditions.setTheta(Math.PI / 3);
assertEquals(Math.PI / 3, conditions.getTheta(), EPSILON);
}
@Test
void testSetAndGetMach() {
conditions.setMach(0.2);
assertEquals(0.2, conditions.getMach(), EPSILON);
assertEquals(0.9797958971, conditions.getBeta(), EPSILON);
conditions.setMach(0.8);
assertEquals(0.8, conditions.getMach(), EPSILON);
assertEquals(0.6, conditions.getBeta(), EPSILON);
conditions.setMach(0.9999999999);
assertEquals(0.9999999999, conditions.getMach(), EPSILON);
assertEquals(0.25, conditions.getBeta(), EPSILON);
conditions.setMach(1.00000000001);
assertEquals(1.00000000001, conditions.getMach(), EPSILON);
assertEquals(0.25, conditions.getBeta(), EPSILON);
conditions.setMach(1.3);
assertEquals(1.3, conditions.getMach(), EPSILON);
assertEquals(0.8306623863, conditions.getBeta(), EPSILON);
conditions.setMach(3);
assertEquals(3, conditions.getMach(), EPSILON);
assertEquals(2.8284271247, conditions.getBeta(), EPSILON);
}
@Test
void testSetAndGetVelocity() {
AtmosphericConditions atm = new AtmosphericConditions();
conditions.setAtmosphericConditions(atm);
double expectedMachSpeed = atm.getMachSpeed();
conditions.setVelocity(expectedMachSpeed / 2);
assertEquals(0.5, conditions.getMach(), EPSILON);
assertEquals(expectedMachSpeed / 2, conditions.getVelocity(), EPSILON);
}
@Test
void testSetAndGetRollRate() {
conditions.setRollRate(5.0);
assertEquals(5.0, conditions.getRollRate(), EPSILON);
}
@Test
void testSetAndGetPitchRate() {
conditions.setPitchRate(2.5);
assertEquals(2.5, conditions.getPitchRate(), EPSILON);
}
@Test
void testSetAndGetYawRate() {
conditions.setYawRate(1.5);
assertEquals(1.5, conditions.getYawRate(), EPSILON);
}
@Test
void testSetAndGetPitchCenter() {
Coordinate center = new Coordinate(1.0, 2.0, 3.0);
conditions.setPitchCenter(center);
assertEquals(center, conditions.getPitchCenter());
}
@Test
void testClone() {
conditions.setAOA(Math.PI / 6);
conditions.setMach(0.7);
conditions.setRollRate(3.0);
AtmosphericConditions atm = new AtmosphericConditions(280, 90000);
conditions.setAtmosphericConditions(atm);
FlightConditions cloned = conditions.clone();
assertNotSame(conditions, cloned);
assertEquals(conditions.getAOA(), cloned.getAOA(), EPSILON);
assertEquals(conditions.getMach(), cloned.getMach(), EPSILON);
assertEquals(conditions.getRollRate(), cloned.getRollRate(), EPSILON);
assertEquals(conditions.getAtmosphericConditions().getTemperature(),
cloned.getAtmosphericConditions().getTemperature(), EPSILON);
assertEquals(conditions.getAtmosphericConditions().getPressure(),
cloned.getAtmosphericConditions().getPressure(), EPSILON);
}
@Test
void testEquals() {
FlightConditions conditions1 = new FlightConditions(null);
FlightConditions conditions2 = new FlightConditions(null);
conditions1.setAOA(Math.PI / 6);
conditions1.setMach(0.7);
conditions2.setAOA(Math.PI / 6);
conditions2.setMach(0.7);
assertTrue(conditions1.equals(conditions2));
conditions2.setMach(0.8);
assertFalse(conditions1.equals(conditions2));
}
@Test
void testSetAndGetAtmosphericConditions() {
AtmosphericConditions atm = new AtmosphericConditions(280, 90000);
conditions.setAtmosphericConditions(atm);
assertEquals(280, conditions.getAtmosphericConditions().getTemperature(), EPSILON);
assertEquals(90000, conditions.getAtmosphericConditions().getPressure(), EPSILON);
}
@Test
void testGetVelocityWithChangedAtmosphere() {
AtmosphericConditions atm = new AtmosphericConditions(280, 90000);
conditions.setAtmosphericConditions(atm);
conditions.setMach(0.5);
double expectedVelocity = 0.5 * atm.getMachSpeed();
assertEquals(expectedVelocity, conditions.getVelocity(), EPSILON);
// Change atmospheric conditions
atm.setTemperature(300);
conditions.setAtmosphericConditions(atm);
// Velocity should change with new atmospheric conditions
expectedVelocity = 0.5 * atm.getMachSpeed();
assertEquals(expectedVelocity, conditions.getVelocity(), EPSILON);
}
}

View File

@ -17,10 +17,10 @@ can export your data for analysis and charting in other packages.
Plotting your rocket's flight Plotting your rocket's flight
============================= =============================
To begin learning about OpenRocket's plotting features, first, click the **Plot / Export** button on the **Flight simulations** window. To begin learning about OpenRocket's plotting features, first, click the :guilabel:`Plot / Export` button on the :guilabel:`Flight simulations` window.
.. figure:: /img/user_guide/advanced_flight_simulation/PlotExportButton.png .. figure:: /img/user_guide/advanced_flight_simulation/PlotExportButton.png
:width: 800 px :width: 400 px
:align: center :align: center
:figclass: or-image-border :figclass: or-image-border
:alt: The Plot / export Button. :alt: The Plot / export Button.
@ -30,7 +30,7 @@ On the **Edit simulation** panel, you'll see tabs marked **Plot data** and **Exp
Plotting data Plotting data
------------- -------------
The **Plot data** tab opens first. Here you can define many parameters that will determine what values are plotted, and The :guilabel:`Plot data` tab opens first. Here you can define many parameters that will determine what values are plotted, and
what events are marked on the plot. what events are marked on the plot.
.. figure:: /img/user_guide/advanced_flight_simulation/PlotExportWindow.png .. figure:: /img/user_guide/advanced_flight_simulation/PlotExportWindow.png
@ -137,8 +137,8 @@ margin for error.
Launch Conditions and Simulation Options Launch Conditions and Simulation Options
======================================== ========================================
From the **Plot data** window, you can click the **<< Edit** button to configure **Launch conditions**, and From the :guilabel:`Plot data` window, you can click the :guilabel:`<< Edit:guilabel:` button to configure :guilabel:`Launch conditions`, and
**Simulation options** before you plot. :guilabel:`Simulation options` before you plot.
Launch conditions Launch conditions
----------------- -----------------
@ -162,7 +162,7 @@ simulation *passes or fails*, when it's evaluated for minimum speed off the rod.
Simulation options Simulation options
------------------ ------------------
In the **Simulation options** tab, the **Simulator options** let you choose the shape of the simulated Earth in your In the :guilabel:`Simulation options` tab, the :guilabel:`Simulator options` let you choose the shape of the simulated Earth in your
calculations (*doing so* **does not** *affect the Earth background in Photo Studio*), and you can choose the time-resolution calculations (*doing so* **does not** *affect the Earth background in Photo Studio*), and you can choose the time-resolution
of the simulation. This is also the place where you add and set up **Simulation extensions**, which are beyond this of the simulation. This is also the place where you add and set up **Simulation extensions**, which are beyond this
guide's purpose. guide's purpose.
@ -180,7 +180,7 @@ guide's purpose.
Exporting Data Exporting Data
============== ==============
Located on the **Plot / export panel**, the **Export Data tab** (shown below) helps you set up a Comma-Separated Value (.csv) Located on the :guilabel:`Plot / export panel`, the :guilabel:`Export Data tab` (shown below) helps you set up a Comma-Separated Value (.csv)
formatted file to export data from your simulations. You can export any or all of over 50 values (generally speaking, formatted file to export data from your simulations. You can export any or all of over 50 values (generally speaking,
the list of parameters above, plus **Coriolis acceleration**). Optional **Comments** sections list any flight events the list of parameters above, plus **Coriolis acceleration**). Optional **Comments** sections list any flight events
(**Apogee**, for example) you selected for your simulation, as well as description and field descriptions. (**Apogee**, for example) you selected for your simulation, as well as description and field descriptions.

View File

@ -425,7 +425,7 @@ Complex rockets fall into two basic categories, a rocket that is propelled by a
simultaneously ignited or multi-staged (massively-staged), propelled by a series of motors that successively ignite the simultaneously ignited or multi-staged (massively-staged), propelled by a series of motors that successively ignite the
next in line when the prior motor burns out. next in line when the prior motor burns out.
.. figure:: /img/user_guide/advanced_rocket_design/xkcd_whatif_24_model_suborbital.png .. figure:: /img/user_guide/advanced_rocket_design/Xkcd_whatif_24_model_suborbital.png
:width: 392 px :width: 392 px
:align: center :align: center
:figclass: or-image-border :figclass: or-image-border
@ -456,23 +456,23 @@ Designing a Rocket with Clustered Motors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OpenRocket makes it easy to design motor clusters. To begin with, add an **Inner Tube** to your aft-most **Body Tube**. OpenRocket makes it easy to design motor clusters. To begin with, add an **Inner Tube** to your aft-most **Body Tube**.
On the **Motor** tab, check the "This component is a motor mount" box. Set its inner diameter to one of the standard On the :guilabel:`Motor` tab, check the "This component is a motor mount" box. Set its inner diameter to one of the standard
motor sizes, unless you have a unique need: 13, 18, 24, 29, 38, 54, 75 or 98mm. Next, click on the **Cluster** tab. motor sizes, unless you have a unique need: 13, 18, 24, 29, 38, 54, 75 or 98mm. Next, click on the :guilabel:`Cluster` tab.
.. figure:: /img/user_guide/advanced_rocket_design/ClusterTab.png .. figure:: /img/user_guide/advanced_rocket_design/ClusterTab.png
:width: 800 px :width: 800 px
:align: center :align: center
:figclass: or-image-border :figclass: or-image-border
:alt: OpenRocket's **Cluster** tab :alt: OpenRocket's Cluster tab
The **Cluster** tab lets you choose a common cluster configuration, and adjust it in your model. When you make an The :guilabel:`Cluster` tab lets you choose a common cluster configuration, and adjust it in your model. When you make an
**Inner Tube** a cluster, you treat every tube in the cluster identically with each addition. If you add an **Inner Tube** a cluster, you treat every tube in the cluster identically with each addition. If you add an
**Engine block** or a **Mass component**, all of the tubes in the cluster will receive one. **Engine block** or a **Mass component**, all of the tubes in the cluster will receive one.
First, pick a cluster configuration from the image tiles on the left side of the tab. Realize that depending upon the First, pick a cluster configuration from the image tiles on the left side of the tab. Realize that depending upon the
sizes of your motor tube and body tube, not every cluster that you can make will fit. sizes of your motor tube and body tube, not every cluster that you can make will fit.
Next, adjust the **Tube separation**. This value controls how close the clustered motors are to each other. A value of Next, adjust the :guilabel:`Tube separation`. This value controls how close the clustered motors are to each other. A value of
1 places the tubes in contact with each other. You can enter decimals like "1.25" in the separation field. In addition 1 places the tubes in contact with each other. You can enter decimals like "1.25" in the separation field. In addition
to potentially affecting your rocket's stability, the **Tube separation** you choose may influence the difficulty of to potentially affecting your rocket's stability, the **Tube separation** you choose may influence the difficulty of
wiring your clustered motors for ignition, and your ability to place adhesive and parts around tightly-packed tubes wiring your clustered motors for ignition, and your ability to place adhesive and parts around tightly-packed tubes
@ -484,7 +484,7 @@ during construction.
:figclass: or-image-border :figclass: or-image-border
:alt: Clustered motor mounts, viewed from aft. :alt: Clustered motor mounts, viewed from aft.
The **Rotation** setting rotates your cluster around the major axis of your rocket (the Up <--> Down one). It's used to The :guilabel:`Rotation` setting rotates your cluster around the major axis of your rocket (the Up <--> Down one). It's used to
line up the motors with other decorative and structural components of your rocket. This alignment may be critical if line up the motors with other decorative and structural components of your rocket. This alignment may be critical if
you're creating a design that ducts eject gasses from one part of the rocket to another. you're creating a design that ducts eject gasses from one part of the rocket to another.

View File

@ -300,7 +300,7 @@ You have now had a brief run through the various components available for use in
In this section we will look at the components used in the *A simple model rocket* example design. To get started, start In this section we will look at the components used in the *A simple model rocket* example design. To get started, start
OpenRocket and navigate to the main window. As a reminder it looks like this: OpenRocket and navigate to the main window. As a reminder it looks like this:
.. figure:: /img/user_guide/basic_rocket_design/Main_window.png .. figure:: /img/user_guide/basic_rocket_design/main_window.png
:width: 95% :width: 95%
:align: center :align: center
:figclass: or-image-border :figclass: or-image-border

View File

@ -19,7 +19,7 @@ Overview
Custom expressions are added to your rocket document from the 'Analyze' menu under custom expressions. This will open a Custom expressions are added to your rocket document from the 'Analyze' menu under custom expressions. This will open a
window showing all your custom expressions. window showing all your custom expressions.
.. figure:: /img/user_guide/custom_expressions/custom_expressions.png .. figure:: /img/user_guide/custom_expressions/Custom_expressions.png
:align: center :align: center
:width: 55% :width: 55%
:figclass: or-image-border :figclass: or-image-border
@ -35,7 +35,7 @@ lower right. This opens the expression builder window. You can also import expre
Building expressions Building expressions
==================== ====================
.. figure:: /img/user_guide/custom_expressions/expression_builder.png .. figure:: /img/user_guide/custom_expressions/Expression_builder.png
:align: center :align: center
:width: 45% :width: 45%
:figclass: or-image-border :figclass: or-image-border

View File

@ -1,10 +1,13 @@
package info.openrocket.swing.gui.configdialog; package info.openrocket.swing.gui.configdialog;
import java.awt.Component;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
@ -19,6 +22,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
@ -31,6 +35,7 @@ import javax.swing.JScrollPane;
import javax.swing.JSpinner; import javax.swing.JSpinner;
import javax.swing.JSplitPane; import javax.swing.JSplitPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
@ -230,11 +235,40 @@ public class FreeformFinSetConfig extends FinSetConfig {
// Create the table // Create the table
tableModel = new FinPointTableModel(); tableModel = new FinPointTableModel();
table = new JTable(tableModel); table = new JTable(tableModel) {
@Override
public void changeSelection(int row, int column, boolean toggle, boolean extend) {
super.changeSelection(row, column, toggle, extend);
if (isCellEditable(row, column)) {
editCellAt(row, column);
Component editor = getEditorComponent();
if (editor != null) {
editor.requestFocus();
}
}
}
};
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
for (int i = 0; i < Columns.values().length; i++) { for (int i = 0; i < Columns.values().length; i++) {
table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth()); table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth());
} }
// Set custom editor for highlighting all text
DefaultCellEditor editor = new DefaultCellEditor(new JTextField()) {
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JTextField textField = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
SwingUtilities.invokeLater(textField::selectAll);
return textField;
}
};
// Apply the editor to all columns
for (int i = 0; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellEditor(editor);
}
table.addMouseListener(new MouseAdapter() { table.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
@ -265,6 +299,17 @@ public class FreeformFinSetConfig extends FinSetConfig {
}); });
JScrollPane tablePane = new JScrollPane(table); JScrollPane tablePane = new JScrollPane(table);
// Remove focus from table when interacting on the figure
figurePane.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
table.clearSelection();
}
});
JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin")); JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin"));
scaleButton.addActionListener(new ActionListener() { scaleButton.addActionListener(new ActionListener() {
@Override @Override
@ -337,9 +382,10 @@ public class FreeformFinSetConfig extends FinSetConfig {
order.add(table); order.add(table);
// row of text directly below figure // row of text directly below figure
panel.add(new StyledLabel(trans.get("lbl.doubleClick1")+" "+trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "spanx 3"); panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 2");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 3"); panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 2, wrap");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 3, wrap"); panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.shiftClickDrag"), -2), "spanx 2");
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlShiftClickDrag"), -2), "spanx 2, wrap");
// row of controls at the bottom of the tab: // row of controls at the bottom of the tab:
panel.add(selector.getAsPanel(), "aligny bottom, gap unrel"); panel.add(selector.getAsPanel(), "aligny bottom, gap unrel");
@ -501,6 +547,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public void mousePressed(MouseEvent event) { public void mousePressed(MouseEvent event) {
requestFocusInWindow();
final FreeformFinSet finset = (FreeformFinSet) component; final FreeformFinSet finset = (FreeformFinSet) component;
final int pressIndex = getPoint(event); final int pressIndex = getPoint(event);
@ -534,13 +581,26 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public void mouseDragged(MouseEvent event) { public void mouseDragged(MouseEvent event) {
int mods = event.getModifiersEx(); int mods = event.getModifiersEx();
if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
if (dragIndex < 0) {
super.mouseDragged(event); super.mouseDragged(event);
return; return;
} }
Point2D.Double point = getCoordinates(event); Point2D.Double point = getCoordinates(event);
final FreeformFinSet finset = (FreeformFinSet) component; final FreeformFinSet finset = (FreeformFinSet) component;
// If shift is held down, apply snapping
if ((mods & MouseEvent.SHIFT_DOWN_MASK) != 0) {
int lockIndex = getLockIndex(mods);
if (lockIndex != -1) {
point = snapPoint(point, finset.getFinPoints()[lockIndex]);
int highlightIndex = getHighlightIndex(lockIndex);
figure.setHighlightIndex(highlightIndex);
}
}
try { try {
finset.setPoint(dragIndex, point.x, point.y); finset.setPoint(dragIndex, point.x, point.y);
} catch (IllegalFinPointException e) { } catch (IllegalFinPointException e) {
@ -552,68 +612,65 @@ public class FreeformFinSetConfig extends FinSetConfig {
updateFields(); updateFields();
// if point is within borders of figure _AND_ outside borders of the ScrollPane's view: // Handle scrolling if point is dragged out of view
final Rectangle dragRectangle = viewport.getViewRect(); handleScrolling();
final Point canvasPoint = new Point( dragPoint.x + dragRectangle.x, dragPoint.y + dragRectangle.y);
if( (figure.getBorderWidth() < canvasPoint.x) && (canvasPoint.x < (figure.getWidth() - figure.getBorderWidth()))
&& (figure.getBorderHeight() < canvasPoint.y) && (canvasPoint.y < (figure.getHeight() - figure.getBorderHeight())))
{
boolean hitBorder = false;
if(dragPoint.x < figure.getBorderWidth()){
hitBorder = true;
dragRectangle.x += dragPoint.x - figure.getBorderWidth();
} else if(dragPoint.x >(dragRectangle.width -figure.getBorderWidth())) {
hitBorder = true;
dragRectangle.x += dragPoint.x - (dragRectangle.width - figure.getBorderWidth());
} }
if (dragPoint.y<figure.getBorderHeight()) { /**
hitBorder = true; * Get the index of the point that the current point should lock to.
dragRectangle.y += dragPoint.y - figure.getBorderHeight(); * @param mods The modifiers of the mouse event
} else if(dragPoint.y >(dragRectangle.height -figure.getBorderHeight())) { * @return The index of the point to lock to, or -1 if no point should be locked to
hitBorder = true; */
dragRectangle.y += dragPoint.y - (dragRectangle.height - figure.getBorderHeight()); private int getLockIndex(int mods) {
} int length = ((FreeformFinSet) component).getFinPoints().length;
if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) {
if (hitBorder) { return (dragIndex > 0 && dragIndex < length - 1) ? dragIndex + 1 : -1;
super.setFitting(false);
selector.update();
figure.scrollRectToVisible(dragRectangle);
revalidate();
}
}
}
@Override
public void componentResized(ComponentEvent e) {
if (fit) {
// if we're fitting the whole figure in the ScrollPane, the parent behavior is fine
super.componentResized(e);
} else if (0 > dragIndex) {
// if we're not _currently_ dragging a point, the parent behavior is fine
super.componentResized(e);
} else { } else {
// currently dragging a point. return (dragIndex < length - 1 && dragIndex > 0) ? dragIndex - 1 : -1;
// ... and if we drag out-of-bounds, we want to move the viewport to keep up }
boolean hitBorder = false;
final Rectangle dragRectangle = viewport.getViewRect();
if(dragPoint.x<figure.getBorderWidth()){
hitBorder = true;
dragRectangle.x += dragPoint.x - figure.getBorderWidth();
} else if(dragPoint.x >(dragRectangle.width -figure.getBorderWidth())) {
hitBorder = true;
dragRectangle.x += dragPoint.x - (dragRectangle.width - figure.getBorderWidth());
} }
if (dragPoint.y<figure.getBorderHeight()) { private int getHighlightIndex(int lockIndex) {
hitBorder = true; return (lockIndex == dragIndex + 1) ? dragIndex : lockIndex;
dragRectangle.y += dragPoint.y - figure.getBorderHeight();
} else if(dragPoint.y >(dragRectangle.height -figure.getBorderHeight())) {
hitBorder = true;
dragRectangle.y += dragPoint.y - (dragRectangle.height - figure.getBorderHeight());
} }
private Point2D.Double snapPoint(Point2D.Double point, Coordinate lockPoint) {
Point2D.Double snappedPoint = new Point2D.Double(point.x, point.y);
double diffX = point.x - lockPoint.x;
double diffY = point.y - lockPoint.y;
double distanceX = Math.abs(diffX);
double distanceY = Math.abs(diffY);
// Calculate distance to 45 or 135 degree line
double a = 1;
double b = (Math.signum(diffX) == Math.signum(diffY)) ? -1 : 1;
double c = -(a * lockPoint.x + b * lockPoint.y);
double distanceDiag = Math.abs(a * point.x + b * point.y + c) / Math.sqrt(2);
// Snap to the closest constraint
if (distanceX <= distanceY && distanceX <= distanceDiag) {
// Snap horizontal
snappedPoint.x = lockPoint.x;
} else if (distanceY <= distanceX && distanceY <= distanceDiag) {
// Snap vertical
snappedPoint.y = lockPoint.y;
} else {
// Snap diagonal (45 degrees)
double avgDist = (Math.abs(diffX) + Math.abs(diffY)) / 2;
snappedPoint.x = lockPoint.x + Math.signum(diffX) * avgDist;
snappedPoint.y = lockPoint.y + Math.signum(diffY) * avgDist;
}
return snappedPoint;
}
private void handleScrolling() {
Rectangle dragRectangle = viewport.getViewRect();
Point canvasPoint = new Point(dragPoint.x + dragRectangle.x, dragPoint.y + dragRectangle.y);
if (isPointWithinFigureBounds(canvasPoint)) {
boolean hitBorder = updateScrollPosition(dragRectangle);
if (hitBorder) { if (hitBorder) {
super.setFitting(false); super.setFitting(false);
selector.update(); selector.update();
@ -623,17 +680,47 @@ public class FreeformFinSetConfig extends FinSetConfig {
} }
} }
private boolean isPointWithinFigureBounds(Point point) {
return figure.getBorderWidth() < point.x && point.x < (figure.getWidth() - figure.getBorderWidth())
&& figure.getBorderHeight() < point.y && point.y < (figure.getHeight() - figure.getBorderHeight());
}
private boolean updateScrollPosition(Rectangle dragRectangle) {
boolean hitBorder = false;
if (dragPoint.x < figure.getBorderWidth()) {
hitBorder = true;
dragRectangle.x += dragPoint.x - figure.getBorderWidth();
} else if (dragPoint.x > (dragRectangle.width - figure.getBorderWidth())) {
hitBorder = true;
dragRectangle.x += dragPoint.x - (dragRectangle.width - figure.getBorderWidth());
}
if (dragPoint.y < figure.getBorderHeight()) {
hitBorder = true;
dragRectangle.y += dragPoint.y - figure.getBorderHeight();
} else if (dragPoint.y > (dragRectangle.height - figure.getBorderHeight())) {
hitBorder = true;
dragRectangle.y += dragPoint.y - (dragRectangle.height - figure.getBorderHeight());
}
return hitBorder;
}
@Override @Override
public void mouseReleased(MouseEvent event) { public void mouseReleased(MouseEvent event) {
dragIndex = -1; dragIndex = -1;
dragPoint = null; dragPoint = null;
figure.setHighlightIndex(-1);
figure.updateFigure();
super.mouseReleased(event); super.mouseReleased(event);
} }
@Override @Override
public void mouseClicked(MouseEvent event) { public void mouseClicked(MouseEvent event) {
int mods = event.getModifiersEx(); int mods = event.getModifiersEx();
if(( event.getButton() == MouseEvent.BUTTON1) && (0 < (MouseEvent.CTRL_DOWN_MASK & mods))) { if ((event.getButton() == MouseEvent.BUTTON1) && (0 < (MouseEvent.CTRL_DOWN_MASK & mods))) {
int clickIndex = getPoint(event); int clickIndex = getPoint(event);
if ( 0 < clickIndex) { if ( 0 < clickIndex) {
// if ctrl+click, delete point // if ctrl+click, delete point
@ -646,7 +733,6 @@ public class FreeformFinSetConfig extends FinSetConfig {
return; return;
} }
} }
super.mouseClicked(event); super.mouseClicked(event);
} }

View File

@ -39,6 +39,7 @@ public class FinPointFigure extends AbstractScaleFigure {
private static final int LINE_WIDTH_FIN_PIXELS = 1; private static final int LINE_WIDTH_FIN_PIXELS = 1;
private static final int LINE_WIDTH_BODY_PIXELS = 2; private static final int LINE_WIDTH_BODY_PIXELS = 2;
private static final int LINE_WIDTH_HIGHLIGHT_PIXELS = 3;
// the size of the boxes around each fin point vertex // the size of the boxes around each fin point vertex
private static final int LINE_WIDTH_BOX_PIXELS = 1; private static final int LINE_WIDTH_BOX_PIXELS = 1;
@ -59,6 +60,7 @@ public class FinPointFigure extends AbstractScaleFigure {
private Rectangle2D.Double[] finPointHandles = null; private Rectangle2D.Double[] finPointHandles = null;
private int selectedIndex = -1; private int selectedIndex = -1;
private int highlightIndex = -1; // The first index of the segment to highlight when snapping to a fin point
private static Color backgroundColor; private static Color backgroundColor;
private static Color finPointBodyLineColor; private static Color finPointBodyLineColor;
@ -66,6 +68,7 @@ public class FinPointFigure extends AbstractScaleFigure {
private static Color finPointGridMinorLineColor; private static Color finPointGridMinorLineColor;
private static Color finPointPointColor; private static Color finPointPointColor;
private static Color finPointSelectedPointColor; private static Color finPointSelectedPointColor;
private static Color finPointSnapHighlightColor;
static { static {
initColors(); initColors();
@ -92,6 +95,7 @@ public class FinPointFigure extends AbstractScaleFigure {
finPointGridMinorLineColor = GUIUtil.getUITheme().getFinPointGridMinorLineColor(); finPointGridMinorLineColor = GUIUtil.getUITheme().getFinPointGridMinorLineColor();
finPointPointColor = GUIUtil.getUITheme().getFinPointPointColor(); finPointPointColor = GUIUtil.getUITheme().getFinPointPointColor();
finPointSelectedPointColor = GUIUtil.getUITheme().getFinPointSelectedPointColor(); finPointSelectedPointColor = GUIUtil.getUITheme().getFinPointSelectedPointColor();
finPointSnapHighlightColor = GUIUtil.getUITheme().getFinPointSnapHighlightColor();
} }
@Override @Override
@ -124,6 +128,7 @@ public class FinPointFigure extends AbstractScaleFigure {
paintRocketBody(g2); paintRocketBody(g2);
paintFinShape(g2); paintFinShape(g2);
paintHighlight(g2);
paintFinHandles(g2); paintFinHandles(g2);
} }
@ -262,6 +267,26 @@ public class FinPointFigure extends AbstractScaleFigure {
g2.draw(shape); g2.draw(shape);
} }
/**
* Paints the highlight line between the two points when snapping to a fin point.
* @param g2 The graphics context to paint to.
*/
private void paintHighlight(final Graphics2D g2) {
final Coordinate[] points = finset.getFinPointsWithRoot();
if (highlightIndex < 0 || highlightIndex > points.length - 1) {
return;
}
Coordinate start = points[highlightIndex];
Coordinate end = points[highlightIndex+1];
final float highlightWidth_m = (float) (LINE_WIDTH_HIGHLIGHT_PIXELS / scale );
g2.setStroke(new BasicStroke(highlightWidth_m));
g2.setColor(finPointSnapHighlightColor);
g2.draw(new Line2D.Double(start.x, start.y, end.x, end.y));
}
private void paintFinHandles(final Graphics2D g2) { private void paintFinHandles(final Graphics2D g2) {
// Excludes fin tab points // Excludes fin tab points
final Coordinate[] drawPoints = finset.getFinPoints(); final Coordinate[] drawPoints = finset.getFinPoints();
@ -438,4 +463,8 @@ public class FinPointFigure extends AbstractScaleFigure {
this.selectedIndex = newIndex; this.selectedIndex = newIndex;
} }
public void setHighlightIndex(final int newIndex) {
this.highlightIndex = newIndex;
}
} }

View File

@ -93,6 +93,7 @@ public class UITheme {
Color getFinPointPointColor(); Color getFinPointPointColor();
Color getFinPointSelectedPointColor(); Color getFinPointSelectedPointColor();
Color getFinPointBodyLineColor(); Color getFinPointBodyLineColor();
Color getFinPointSnapHighlightColor();
Icon getMassOverrideIcon(); Icon getMassOverrideIcon();
Icon getMassOverrideSubcomponentIcon(); Icon getMassOverrideSubcomponentIcon();
@ -404,6 +405,11 @@ public class UITheme {
return Color.BLACK; return Color.BLACK;
} }
@Override
public Color getFinPointSnapHighlightColor() {
return Color.RED;
}
@Override @Override
public Icon getMassOverrideIcon() { public Icon getMassOverrideIcon() {
return Icons.MASS_OVERRIDE_LIGHT; return Icons.MASS_OVERRIDE_LIGHT;
@ -805,6 +811,11 @@ public class UITheme {
return Color.WHITE; return Color.WHITE;
} }
@Override
public Color getFinPointSnapHighlightColor() {
return new Color(255, 58, 58, 255);
}
@Override @Override
public Icon getMassOverrideIcon() { public Icon getMassOverrideIcon() {
return Icons.MASS_OVERRIDE_DARK; return Icons.MASS_OVERRIDE_DARK;
@ -1206,6 +1217,11 @@ public class UITheme {
return Color.WHITE; return Color.WHITE;
} }
@Override
public Color getFinPointSnapHighlightColor() {
return new Color(241, 77, 77, 255);
}
@Override @Override
public Icon getMassOverrideIcon() { public Icon getMassOverrideIcon() {
return Icons.MASS_OVERRIDE_DARK; return Icons.MASS_OVERRIDE_DARK;
@ -1626,6 +1642,11 @@ public class UITheme {
return getCurrentTheme().getFinPointBodyLineColor(); return getCurrentTheme().getFinPointBodyLineColor();
} }
@Override
public Color getFinPointSnapHighlightColor() {
return getCurrentTheme().getFinPointBodyLineColor();
}
@Override @Override
public Icon getMassOverrideIcon() { public Icon getMassOverrideIcon() {
return getCurrentTheme().getMassOverrideIcon(); return getCurrentTheme().getMassOverrideIcon();