Merge branch 'unstable' into no-tumble-warning
This commit is contained in:
commit
edf4d03322
@ -22,6 +22,7 @@ import info.openrocket.core.util.ModID;
|
||||
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
|
||||
*/
|
||||
public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
|
||||
private static final double MIN_BETA = 0.25;
|
||||
|
||||
private List<EventListener> listenerList = new ArrayList<>();
|
||||
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(M^2 - 1) for M>1
|
||||
*/
|
||||
private double beta = MathUtil.safeSqrt(1 - mach * mach);
|
||||
private double beta = calculateBeta(mach);
|
||||
|
||||
/** Current roll rate. */
|
||||
private double rollRate = 0;
|
||||
@ -243,10 +244,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
|
||||
return;
|
||||
|
||||
this.mach = mach;
|
||||
if (mach < 1)
|
||||
this.beta = MathUtil.safeSqrt(1 - mach * mach);
|
||||
else
|
||||
this.beta = MathUtil.safeSqrt(mach * mach - 1);
|
||||
this.beta = calculateBeta(mach);
|
||||
|
||||
fireChangeEvent();
|
||||
}
|
||||
@ -288,6 +286,19 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
|
||||
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.
|
||||
*/
|
||||
|
||||
@ -162,9 +162,6 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
// TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25
|
||||
// Calculate CP position
|
||||
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
|
||||
@ -176,17 +173,12 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
forces.setCrollForce((macSpan + r) * cna1 * (1 + tau) * cantAngle / conditions.getRefLength());
|
||||
|
||||
if (conditions.getAOA() > STALL_ANGLE) {
|
||||
// System.out.println("Fin stalling in roll");
|
||||
forces.setCrollForce(forces.getCrollForce() * MathUtil.clamp(
|
||||
1 - (conditions.getAOA() - STALL_ANGLE) / (STALL_ANGLE / 2), 0, 1));
|
||||
}
|
||||
forces.setCrollDamp(calculateDampingMoment(conditions));
|
||||
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.setCN(cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE));
|
||||
forces.setCP(new Coordinate(x, 0, 0, cna));
|
||||
@ -349,7 +341,6 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
double y = i * dy;
|
||||
|
||||
macLength += length * length;
|
||||
//logger.debug("macLength = {}, length = {}, i = {}", macLength, length, i);
|
||||
macSpan += y * length;
|
||||
macLead += chordLead[i] * length;
|
||||
area += length;
|
||||
@ -408,10 +399,6 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
K1 = new LinearInterpolator(x, k1);
|
||||
K2 = new LinearInterpolator(x, k2);
|
||||
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) {
|
||||
@ -445,8 +432,6 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -473,24 +458,16 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
}
|
||||
sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() /
|
||||
(conditions.getRefArea() * conditions.getRefLength());
|
||||
|
||||
// System.out.println("SPECIAL: " +
|
||||
// (MathUtil.sign(rollRate) * sum));
|
||||
|
||||
return MathUtil.sign(rollRate) * sum;
|
||||
}
|
||||
|
||||
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 /
|
||||
(conditions.getRefArea() * conditions.getRefLength() *
|
||||
conditions.getVelocity() * conditions.getBeta());
|
||||
}
|
||||
if (mach >= CNA_SUPERSONIC) {
|
||||
|
||||
double vel = conditions.getVelocity();
|
||||
double k1 = K1.getValue(mach);
|
||||
double k2 = K2.getValue(mach);
|
||||
@ -511,7 +488,6 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
}
|
||||
|
||||
// Transonic, do linear interpolation
|
||||
|
||||
FlightConditions cond = conditions.clone();
|
||||
cond.setMach(CNA_SUBSONIC - 0.01);
|
||||
double subsonic = calculateDampingMoment(cond);
|
||||
@ -532,7 +508,7 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
*/
|
||||
private double calculateCPPos(FlightConditions cond) {
|
||||
double m = cond.getMach();
|
||||
// logger.debug("m = {} ", m);
|
||||
|
||||
if (m <= 0.5) {
|
||||
// At subsonic speeds CP at quarter chord
|
||||
return 0.25;
|
||||
@ -551,7 +527,7 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
val += v * x;
|
||||
x *= m;
|
||||
}
|
||||
// logger.debug("val = {}", val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@ -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.FincrossSection = Fin cross section:
|
||||
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.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.exportCSV = Export CSV
|
||||
FreeformFinSetConfig.lbl.deletePoint = Delete point
|
||||
|
||||
@ -1033,9 +1033,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = :الموقع بالنسبة إلى
|
||||
FreeformFinSetCfg.lbl.plus = plus
|
||||
FreeformFinSetCfg.lbl.FincrossSection = :المقطع العرضي للزعنفة
|
||||
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.ctrlClick = مفتاح التحكم مع النقر: لحذف نقطة
|
||||
FreeformFinSetConfig.lbl.scaleFin = حَجِّمْ الزعنفة
|
||||
|
||||
@ -715,9 +715,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Pozice vzhledem k:
|
||||
FreeformFinSetCfg.lbl.plus = plus
|
||||
FreeformFinSetCfg.lbl.FincrossSection = Hrany stabilizátoru:
|
||||
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.ctrlClick = Ctrl+klik: Odstran bod
|
||||
FreeformFinSetConfig.lbl.scaleFin = Merítko stabilizátoru
|
||||
|
||||
@ -772,9 +772,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Position relativ zu:
|
||||
FreeformFinSetCfg.lbl.plus = plus
|
||||
FreeformFinSetCfg.lbl.FincrossSection = Querschnitt:
|
||||
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.ctrlClick = Strg+Klick: Punkt löschen
|
||||
FreeformFinSetConfig.lbl.scaleFin = Leitwerk skalieren
|
||||
|
||||
@ -374,9 +374,6 @@ FreeformFinSetCfg.tab.ttip.General = Propiedades generales
|
||||
|
||||
FreeformFinSetConfig.lbl.clickDrag = Click (sobre l\u00ednea)+arrastrar: Agregar 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
|
||||
|
||||
GeneralOptimizationDialog.basicSimulationName = Simulaci\u00f3n b\u00e1sica
|
||||
|
||||
@ -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.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
|
||||
|
||||
GeneralOptimizationDialog.basicSimulationName = Simulation simple
|
||||
|
||||
@ -774,9 +774,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Posizione relativa a :
|
||||
FreeformFinSetCfg.lbl.plus = pi\u00f9
|
||||
FreeformFinSetCfg.lbl.FincrossSection = Sezione delle pinne
|
||||
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.ctrlClick = Ctrl+click: rimuove punti
|
||||
FreeformFinSetConfig.lbl.scaleFin = Scala pinna
|
||||
|
||||
@ -804,9 +804,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = \u4F4D\u7F6E\uFF1A
|
||||
FreeformFinSetCfg.lbl.plus = \u30D7\u30E9\u30B9
|
||||
FreeformFinSetCfg.lbl.FincrossSection = \u30D5\u30A3\u30F3\u65AD\u9762\u7A4D\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.ctrlClick = Ctrl+click: \u30DD\u30A4\u30F3\u30C8\u306E\u524A\u9664
|
||||
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
|
||||
|
||||
@ -984,9 +984,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Positie relatief t.o.v.:
|
||||
FreeformFinSetCfg.lbl.plus = plus
|
||||
FreeformFinSetCfg.lbl.FincrossSection = Vindoorsnede:
|
||||
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.ctrlClick = Ctrl+klik: Verwijder punt
|
||||
FreeformFinSetConfig.lbl.scaleFin = Schaal vin
|
||||
|
||||
@ -719,9 +719,6 @@ ComponentInfo.EngineBlock = <b>Blokada silnika</b> unieruchamia silnik wewn\u01
|
||||
FreeformFinSetCfg.lbl.plus = plus
|
||||
FreeformFinSetCfg.lbl.FincrossSection = Przekrój statecznika:
|
||||
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.ctrlClick = Ctrl+klik: Usu\u0144 punkt
|
||||
FreeformFinSetConfig.lbl.scaleFin = Skaluj statecznik
|
||||
|
||||
@ -354,9 +354,6 @@ FreeformFinSetCfg.tab.ttip.General = Propriedades gerais
|
||||
|
||||
FreeformFinSetConfig.lbl.clickDrag = Clique+arraste: Adicionar e mover pontos
|
||||
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
|
||||
|
||||
GeneralOptimizationDialog.basicSimulationName = Simula\u00e7\u00e3o b\u00e1sica
|
||||
|
||||
@ -1013,9 +1013,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = \u041F\u043E\u043B\u043E\u0436\u0435\u043D
|
||||
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.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.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
|
||||
|
||||
@ -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.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:
|
||||
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.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
|
||||
|
||||
@ -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.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
|
||||
|
||||
GeneralOptimizationDialog.basicSimulationName = \u57FA\u672C\u4EFF\u771F
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -17,10 +17,10 @@ can export your data for analysis and charting in other packages.
|
||||
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
|
||||
:width: 800 px
|
||||
:width: 400 px
|
||||
:align: center
|
||||
:figclass: or-image-border
|
||||
: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
|
||||
-------------
|
||||
|
||||
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.
|
||||
|
||||
.. figure:: /img/user_guide/advanced_flight_simulation/PlotExportWindow.png
|
||||
@ -137,8 +137,8 @@ margin for error.
|
||||
Launch Conditions and Simulation Options
|
||||
========================================
|
||||
|
||||
From the **Plot data** window, you can click the **<< Edit** button to configure **Launch conditions**, and
|
||||
**Simulation options** before you plot.
|
||||
From the :guilabel:`Plot data` window, you can click the :guilabel:`<< Edit:guilabel:` button to configure :guilabel:`Launch conditions`, and
|
||||
:guilabel:`Simulation options` before you plot.
|
||||
|
||||
Launch conditions
|
||||
-----------------
|
||||
@ -162,7 +162,7 @@ simulation *passes or fails*, when it's evaluated for minimum speed off the rod.
|
||||
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
|
||||
of the simulation. This is also the place where you add and set up **Simulation extensions**, which are beyond this
|
||||
guide's purpose.
|
||||
@ -180,7 +180,7 @@ guide's purpose.
|
||||
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,
|
||||
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.
|
||||
|
||||
@ -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
|
||||
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
|
||||
:align: center
|
||||
: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**.
|
||||
On the **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.
|
||||
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 :guilabel:`Cluster` tab.
|
||||
|
||||
.. figure:: /img/user_guide/advanced_rocket_design/ClusterTab.png
|
||||
:width: 800 px
|
||||
:align: center
|
||||
: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
|
||||
**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
|
||||
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
|
||||
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
|
||||
@ -484,7 +484,7 @@ during construction.
|
||||
:figclass: or-image-border
|
||||
: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
|
||||
you're creating a design that ducts eject gasses from one part of the rocket to another.
|
||||
|
||||
|
||||
@ -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
|
||||
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%
|
||||
:align: center
|
||||
:figclass: or-image-border
|
||||
|
||||
@ -19,7 +19,7 @@ Overview
|
||||
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.
|
||||
|
||||
.. figure:: /img/user_guide/custom_expressions/custom_expressions.png
|
||||
.. figure:: /img/user_guide/custom_expressions/Custom_expressions.png
|
||||
:align: center
|
||||
:width: 55%
|
||||
:figclass: or-image-border
|
||||
@ -35,7 +35,7 @@ lower right. This opens the expression builder window. You can also import expre
|
||||
Building expressions
|
||||
====================
|
||||
|
||||
.. figure:: /img/user_guide/custom_expressions/expression_builder.png
|
||||
.. figure:: /img/user_guide/custom_expressions/Expression_builder.png
|
||||
:align: center
|
||||
:width: 45%
|
||||
:figclass: or-image-border
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
package info.openrocket.swing.gui.configdialog;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
@ -19,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.DefaultCellEditor;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
@ -31,6 +35,7 @@ import javax.swing.JScrollPane;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
@ -230,11 +235,40 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
|
||||
// Create the table
|
||||
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);
|
||||
for (int i = 0; i < Columns.values().length; i++) {
|
||||
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() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
@ -264,6 +298,17 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
}
|
||||
});
|
||||
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"));
|
||||
scaleButton.addActionListener(new ActionListener() {
|
||||
@ -337,9 +382,10 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
order.add(table);
|
||||
|
||||
// row of text directly below figure
|
||||
panel.add(new StyledLabel(trans.get("lbl.doubleClick1")+" "+trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "spanx 3");
|
||||
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 3");
|
||||
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 3, wrap");
|
||||
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 2");
|
||||
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 2, 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:
|
||||
panel.add(selector.getAsPanel(), "aligny bottom, gap unrel");
|
||||
@ -501,6 +547,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent event) {
|
||||
requestFocusInWindow();
|
||||
final FreeformFinSet finset = (FreeformFinSet) component;
|
||||
|
||||
final int pressIndex = getPoint(event);
|
||||
@ -534,13 +581,26 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent event) {
|
||||
int mods = event.getModifiersEx();
|
||||
if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) {
|
||||
|
||||
if (dragIndex < 0) {
|
||||
super.mouseDragged(event);
|
||||
return;
|
||||
}
|
||||
|
||||
Point2D.Double point = getCoordinates(event);
|
||||
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 {
|
||||
finset.setPoint(dragIndex, point.x, point.y);
|
||||
} catch (IllegalFinPointException e) {
|
||||
@ -552,29 +612,65 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
|
||||
updateFields();
|
||||
|
||||
// if point is within borders of figure _AND_ outside borders of the ScrollPane's view:
|
||||
final Rectangle dragRectangle = viewport.getViewRect();
|
||||
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());
|
||||
}
|
||||
// Handle scrolling if point is dragged out of view
|
||||
handleScrolling();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
/**
|
||||
* Get the index of the point that the current point should lock to.
|
||||
* @param mods The modifiers of the mouse event
|
||||
* @return The index of the point to lock to, or -1 if no point should be locked to
|
||||
*/
|
||||
private int getLockIndex(int mods) {
|
||||
int length = ((FreeformFinSet) component).getFinPoints().length;
|
||||
if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) {
|
||||
return (dragIndex > 0 && dragIndex < length - 1) ? dragIndex + 1 : -1;
|
||||
} else {
|
||||
return (dragIndex < length - 1 && dragIndex > 0) ? dragIndex - 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int getHighlightIndex(int lockIndex) {
|
||||
return (lockIndex == dragIndex + 1) ? dragIndex : lockIndex;
|
||||
}
|
||||
|
||||
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) {
|
||||
super.setFitting(false);
|
||||
selector.update();
|
||||
@ -584,56 +680,47 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
// currently dragging a point.
|
||||
// ... and if we drag out-of-bounds, we want to move the viewport to keep up
|
||||
boolean hitBorder = false;
|
||||
final Rectangle dragRectangle = viewport.getViewRect();
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
private boolean updateScrollPosition(Rectangle dragRectangle) {
|
||||
boolean hitBorder = false;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
if (hitBorder) {
|
||||
super.setFitting(false);
|
||||
selector.update();
|
||||
figure.scrollRectToVisible(dragRectangle);
|
||||
revalidate();
|
||||
}
|
||||
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
|
||||
public void mouseReleased(MouseEvent event) {
|
||||
dragIndex = -1;
|
||||
dragPoint = null;
|
||||
figure.setHighlightIndex(-1);
|
||||
figure.updateFigure();
|
||||
|
||||
super.mouseReleased(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event) {
|
||||
int mods = event.getModifiersEx();
|
||||
if(( event.getButton() == MouseEvent.BUTTON1) && (0 < (MouseEvent.CTRL_DOWN_MASK & mods))) {
|
||||
int mods = event.getModifiersEx();
|
||||
if ((event.getButton() == MouseEvent.BUTTON1) && (0 < (MouseEvent.CTRL_DOWN_MASK & mods))) {
|
||||
int clickIndex = getPoint(event);
|
||||
if ( 0 < clickIndex) {
|
||||
// if ctrl+click, delete point
|
||||
@ -646,8 +733,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.mouseClicked(event);
|
||||
super.mouseClicked(event);
|
||||
}
|
||||
|
||||
private int getPoint(MouseEvent event) {
|
||||
|
||||
@ -39,6 +39,7 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
|
||||
private static final int LINE_WIDTH_FIN_PIXELS = 1;
|
||||
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
|
||||
private static final int LINE_WIDTH_BOX_PIXELS = 1;
|
||||
@ -59,6 +60,7 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
|
||||
private Rectangle2D.Double[] finPointHandles = null;
|
||||
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 finPointBodyLineColor;
|
||||
@ -66,6 +68,7 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
private static Color finPointGridMinorLineColor;
|
||||
private static Color finPointPointColor;
|
||||
private static Color finPointSelectedPointColor;
|
||||
private static Color finPointSnapHighlightColor;
|
||||
|
||||
static {
|
||||
initColors();
|
||||
@ -92,6 +95,7 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
finPointGridMinorLineColor = GUIUtil.getUITheme().getFinPointGridMinorLineColor();
|
||||
finPointPointColor = GUIUtil.getUITheme().getFinPointPointColor();
|
||||
finPointSelectedPointColor = GUIUtil.getUITheme().getFinPointSelectedPointColor();
|
||||
finPointSnapHighlightColor = GUIUtil.getUITheme().getFinPointSnapHighlightColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -124,6 +128,7 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
paintRocketBody(g2);
|
||||
|
||||
paintFinShape(g2);
|
||||
paintHighlight(g2);
|
||||
paintFinHandles(g2);
|
||||
}
|
||||
|
||||
@ -261,6 +266,26 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
g2.setColor(finPointBodyLineColor);
|
||||
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) {
|
||||
// Excludes fin tab points
|
||||
@ -438,4 +463,8 @@ public class FinPointFigure extends AbstractScaleFigure {
|
||||
this.selectedIndex = newIndex;
|
||||
}
|
||||
|
||||
public void setHighlightIndex(final int newIndex) {
|
||||
this.highlightIndex = newIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@ public class UITheme {
|
||||
Color getFinPointPointColor();
|
||||
Color getFinPointSelectedPointColor();
|
||||
Color getFinPointBodyLineColor();
|
||||
Color getFinPointSnapHighlightColor();
|
||||
|
||||
Icon getMassOverrideIcon();
|
||||
Icon getMassOverrideSubcomponentIcon();
|
||||
@ -404,6 +405,11 @@ public class UITheme {
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getFinPointSnapHighlightColor() {
|
||||
return Color.RED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getMassOverrideIcon() {
|
||||
return Icons.MASS_OVERRIDE_LIGHT;
|
||||
@ -805,6 +811,11 @@ public class UITheme {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getFinPointSnapHighlightColor() {
|
||||
return new Color(255, 58, 58, 255);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getMassOverrideIcon() {
|
||||
return Icons.MASS_OVERRIDE_DARK;
|
||||
@ -1206,6 +1217,11 @@ public class UITheme {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getFinPointSnapHighlightColor() {
|
||||
return new Color(241, 77, 77, 255);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getMassOverrideIcon() {
|
||||
return Icons.MASS_OVERRIDE_DARK;
|
||||
@ -1626,6 +1642,11 @@ public class UITheme {
|
||||
return getCurrentTheme().getFinPointBodyLineColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getFinPointSnapHighlightColor() {
|
||||
return getCurrentTheme().getFinPointBodyLineColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getMassOverrideIcon() {
|
||||
return getCurrentTheme().getMassOverrideIcon();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user