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

View File

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

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

View File

@ -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 = حَجِّمْ الزعنفة

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

View File

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

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

View File

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