From e51790e69dd06b70bc38c7b9e3b709d6029dce4d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 8 Oct 2024 19:03:06 +0200 Subject: [PATCH 01/16] Clamp beta factor in transonic region --- .../core/aerodynamics/FlightConditions.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java index 4e18d2914..f390b9d6c 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java @@ -55,7 +55,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 +243,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 +285,23 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable { return beta; } + /** + * Calculate the beta value (compressibility factor/Prandtl-Glauert correction factor) for the given Mach number. + * Source: "Active Guidance and Dynamic Flight Mechanics for Model Rockets", David Ketchledge (1993) + * @param mach the Mach number. + * @return the beta value. + */ + private static double calculateBeta(double mach) { + if (mach < 0.8) { + return MathUtil.safeSqrt(1 - mach * mach); + } else if (mach < 1.1) + // Because beta reaches infinity for M=1, we clamp the factor to 0.6 in the transonic region (Mach 0.8-1.1) + return 0.6; // sqrt(1 - 0.8^2) + else { + return MathUtil.safeSqrt(mach * mach - 1); + } + } + /** * @return the current roll rate. */ From ab5fa61d3e40555ce93e8283ff36f1ede8faa12d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 8 Oct 2024 19:05:18 +0200 Subject: [PATCH 02/16] Remove debug prints --- .../aerodynamics/barrowman/FinSetCalc.java | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java index cfc0cc8b9..c5f783e80 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java @@ -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,18 +458,11 @@ 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()); @@ -511,7 +489,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 +509,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 +528,7 @@ public class FinSetCalc extends RocketComponentCalc { val += v * x; x *= m; } - // logger.debug("val = {}", val); + return val; } From 8b3b763cc8c16835b56fbcf46825905f8fc83e1a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 8 Oct 2024 19:06:17 +0200 Subject: [PATCH 03/16] Also apply Prandtl correction at supersonic speed --- .../openrocket/core/aerodynamics/barrowman/FinSetCalc.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java index c5f783e80..2551a194f 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java @@ -468,8 +468,7 @@ public class FinSetCalc extends RocketComponentCalc { conditions.getVelocity() * conditions.getBeta()); } if (mach >= CNA_SUPERSONIC) { - - double vel = conditions.getVelocity(); + double vel = conditions.getVelocity() * conditions.getBeta(); double k1 = K1.getValue(mach); double k2 = K2.getValue(mach); double k3 = K3.getValue(mach); From ecc203b621809a761784717d7b14ec961c6d4d1b Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 8 Oct 2024 23:15:29 +0200 Subject: [PATCH 04/16] Add unit tests for FlightConditions --- .../aerodynamics/FlightConditionsTest.java | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java diff --git a/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java b/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java new file mode 100644 index 000000000..264424eb2 --- /dev/null +++ b/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java @@ -0,0 +1,193 @@ +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.8); + assertEquals(0.8, conditions.getMach(), EPSILON); + assertEquals(0.6, 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); + } +} \ No newline at end of file From c7540868a3ad70ecb5bf4a72ac072e01af34021b Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 8 Oct 2024 23:42:17 +0200 Subject: [PATCH 05/16] Fix some bugs in the documentation --- .../user_guide/advanced_flight_simulation.rst | 14 +++++++------- docs/source/user_guide/advanced_rocket_design.rst | 14 +++++++------- docs/source/user_guide/basic_rocket_design.rst | 2 +- docs/source/user_guide/custom_expressions.rst | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/source/user_guide/advanced_flight_simulation.rst b/docs/source/user_guide/advanced_flight_simulation.rst index b1f24a016..ec3b0ffd6 100644 --- a/docs/source/user_guide/advanced_flight_simulation.rst +++ b/docs/source/user_guide/advanced_flight_simulation.rst @@ -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. diff --git a/docs/source/user_guide/advanced_rocket_design.rst b/docs/source/user_guide/advanced_rocket_design.rst index 88734974a..0b945f941 100644 --- a/docs/source/user_guide/advanced_rocket_design.rst +++ b/docs/source/user_guide/advanced_rocket_design.rst @@ -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. diff --git a/docs/source/user_guide/basic_rocket_design.rst b/docs/source/user_guide/basic_rocket_design.rst index fd78ab8f2..4a5c6cef6 100644 --- a/docs/source/user_guide/basic_rocket_design.rst +++ b/docs/source/user_guide/basic_rocket_design.rst @@ -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 diff --git a/docs/source/user_guide/custom_expressions.rst b/docs/source/user_guide/custom_expressions.rst index 73a9c4e63..9c2aa6394 100644 --- a/docs/source/user_guide/custom_expressions.rst +++ b/docs/source/user_guide/custom_expressions.rst @@ -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 From 65ebf9de721f7138350f5ee7ee5b54c7736a74a4 Mon Sep 17 00:00:00 2001 From: "neil.weinstock@gmail.com" Date: Tue, 8 Oct 2024 20:57:49 -0400 Subject: [PATCH 06/16] Implement angle constraints in fin editor --- .../main/resources/l10n/messages.properties | 6 +- .../configdialog/FreeformFinSetConfig.java | 56 ++++++++++++++++++- .../swing/gui/scalefigure/FinPointFigure.java | 23 ++++++++ 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 58c287ad2..68c17d295 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -1260,9 +1260,11 @@ FreeformFinSetCfg.lbl.ttip.Fincant = The angle that the fins are canted with res 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.doubleClick2 = to edit table entry 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 diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java index b43cc420b..0e3c4f778 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java @@ -337,9 +337,11 @@ 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("lbl.doubleClick1")+" "+trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "spanx 2"); + panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 2, wrap"); + panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 2"); + 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"); @@ -534,13 +536,58 @@ public class FreeformFinSetConfig extends FinSetConfig { @Override public void mouseDragged(MouseEvent event) { int mods = event.getModifiersEx(); + /*XXX if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) { super.mouseDragged(event); return; } + */ Point2D.Double point = getCoordinates(event); final FreeformFinSet finset = (FreeformFinSet) component; + + // If shift is held down and a point is being dragged, constrain angle relative to previous or following point + int lockIndex = -1; + int highlightIndex = -1; + if (dragIndex >= 0 && (mods & MouseEvent.SHIFT_DOWN_MASK) != 0) { + if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) { + if (dragIndex < finset.getFinPoints().length-1) { + lockIndex = dragIndex + 1; + highlightIndex = dragIndex; + } + } + else if (dragIndex > 0) { + lockIndex = dragIndex - 1; + highlightIndex = dragIndex - 1; + } + + if (lockIndex >= 0) { + // Fetch point to lock to + final Coordinate lockPoint = finset.getFinPoints()[lockIndex]; + // Distances to vertical and horizontal lines + final double diffX = point.x - lockPoint.x; + final double diffY = point.y - lockPoint.y; + final double distanceX = Math.abs(diffX); + final double distanceY = Math.abs(diffY); + // Calculate distance to 45 or 135 degree line, as appropriate + final double a = 1; // always + final double b = (Math.signum(diffX) == Math.signum(diffY)) ? -1 : 1; + final double c = -(a*lockPoint.x + b*lockPoint.y); + final double distanceDiag = Math.abs(a*point.x + b*point.y + c) / Math.sqrt(2); + + // Snap in the appropriate direction + if (distanceX <= distanceY && distanceX <= distanceDiag) // snap horizontal + point.x = lockPoint.x; + else if (distanceY <= distanceX && distanceY <= distanceDiag) // snap vertical + point.y = lockPoint.y; + else { // snap diagonal + point.x = (b*( b*point.x - a*point.y) - a*c) / 2; + point.y = (a*(-b*point.x + a*point.y) - b*c) / 2; + } + } + } + figure.setHighlightIndex(highlightIndex); + try { finset.setPoint(dragIndex, point.x, point.y); } catch (IllegalFinPointException e) { @@ -627,6 +674,9 @@ public class FreeformFinSetConfig extends FinSetConfig { public void mouseReleased(MouseEvent event) { dragIndex = -1; dragPoint = null; + figure.setHighlightIndex(-1); + figure.updateFigure(); + super.mouseReleased(event); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java index 11b7a214f..4f3911ab7 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java +++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java @@ -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; private static Color backgroundColor; private static Color finPointBodyLineColor; @@ -124,6 +126,7 @@ public class FinPointFigure extends AbstractScaleFigure { paintRocketBody(g2); paintFinShape(g2); + paintHighlight(g2); paintFinHandles(g2); } @@ -261,6 +264,22 @@ public class FinPointFigure extends AbstractScaleFigure { g2.setColor(finPointBodyLineColor); g2.draw(shape); } + + 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(Color.RED); + 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 +457,8 @@ public class FinPointFigure extends AbstractScaleFigure { this.selectedIndex = newIndex; } + public void setHighlightIndex(final int newIndex) { + this.highlightIndex = newIndex; + } + } From efc043d2b4e902b96bfac8ab5e02e02db6eb7bc4 Mon Sep 17 00:00:00 2001 From: "neil.weinstock@gmail.com" Date: Tue, 8 Oct 2024 21:54:56 -0400 Subject: [PATCH 07/16] Small fix --- .../swing/gui/configdialog/FreeformFinSetConfig.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java index 0e3c4f778..c3f24acf0 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java @@ -536,12 +536,11 @@ public class FreeformFinSetConfig extends FinSetConfig { @Override public void mouseDragged(MouseEvent event) { int mods = event.getModifiersEx(); - /*XXX - 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; @@ -549,7 +548,7 @@ public class FreeformFinSetConfig extends FinSetConfig { // If shift is held down and a point is being dragged, constrain angle relative to previous or following point int lockIndex = -1; int highlightIndex = -1; - if (dragIndex >= 0 && (mods & MouseEvent.SHIFT_DOWN_MASK) != 0) { + if ((mods & MouseEvent.SHIFT_DOWN_MASK) != 0) { if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) { if (dragIndex < finset.getFinPoints().length-1) { lockIndex = dragIndex + 1; From 56aee518e7dd50af130dede5800e3c069d4eef00 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 9 Oct 2024 22:56:22 +0200 Subject: [PATCH 08/16] Whoops, don't use compressibility correction at supersonic flow --- .../info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java index 2551a194f..6a86afb29 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/FinSetCalc.java @@ -468,7 +468,7 @@ public class FinSetCalc extends RocketComponentCalc { conditions.getVelocity() * conditions.getBeta()); } if (mach >= CNA_SUPERSONIC) { - double vel = conditions.getVelocity() * conditions.getBeta(); + double vel = conditions.getVelocity(); double k1 = K1.getValue(mach); double k2 = K2.getValue(mach); double k3 = K3.getValue(mach); From a5eae084569c5725168782b1666edef5d0511e27 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 9 Oct 2024 22:56:55 +0200 Subject: [PATCH 09/16] Clamp Mach to avoid beta singularity --- .../core/aerodynamics/FlightConditions.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java index f390b9d6c..e68ca211c 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java @@ -287,17 +287,16 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable { /** * Calculate the beta value (compressibility factor/Prandtl-Glauert correction factor) for the given Mach number. - * Source: "Active Guidance and Dynamic Flight Mechanics for Model Rockets", David Ketchledge (1993) * @param mach the Mach number. * @return the beta value. */ private static double calculateBeta(double mach) { - if (mach < 0.8) { + if (mach < 0.99999) { return MathUtil.safeSqrt(1 - mach * mach); - } else if (mach < 1.1) - // Because beta reaches infinity for M=1, we clamp the factor to 0.6 in the transonic region (Mach 0.8-1.1) - return 0.6; // sqrt(1 - 0.8^2) - else { + } else if (mach < 1.00001) { + // Clamp to avoid singularity near Mach 1 + return MathUtil.safeSqrt(1 - 0.99999 * 0.99999); + } else { return MathUtil.safeSqrt(mach * mach - 1); } } From 7c53e6017bec0cae107d6a6f3dd7507c9c44394d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Oct 2024 01:01:59 +0200 Subject: [PATCH 10/16] Clamp beta to minimal value to avoid singularity --- .../openrocket/core/aerodynamics/FlightConditions.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java index e68ca211c..b91978fb8 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java @@ -22,6 +22,7 @@ import info.openrocket.core.util.ModID; * @author Sampo Niskanen */ public class FlightConditions implements Cloneable, ChangeSource, Monitorable { + private static final double MIN_BETA = 0.25; private List listenerList = new ArrayList<>(); private EventObject event = new EventObject(this); @@ -291,13 +292,10 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable { * @return the beta value. */ private static double calculateBeta(double mach) { - if (mach < 0.99999) { - return MathUtil.safeSqrt(1 - mach * mach); - } else if (mach < 1.00001) { - // Clamp to avoid singularity near Mach 1 - return MathUtil.safeSqrt(1 - 0.99999 * 0.99999); + if (mach < 1) { + return MathUtil.max(MIN_BETA, MathUtil.safeSqrt(1 - mach * mach)); } else { - return MathUtil.safeSqrt(mach * mach - 1); + return MathUtil.max(MIN_BETA, MathUtil.safeSqrt(mach * mach - 1)); } } From 64d7c4bb80d12051b6ea78a3dc89e33c9cf4d50a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Oct 2024 01:06:25 +0200 Subject: [PATCH 11/16] Update unit tests for more beta testing --- .../aerodynamics/FlightConditionsTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java b/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java index 264424eb2..6ea81d26b 100644 --- a/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java +++ b/core/src/test/java/info/openrocket/core/aerodynamics/FlightConditionsTest.java @@ -86,9 +86,29 @@ class FlightConditionsTest { @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 From f4b4e3ccc07daa6e864c184ef8c8ad858065a8ba Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Oct 2024 13:02:29 +0200 Subject: [PATCH 12/16] Allow table editing with single click + fix tab traversal in table --- .../main/resources/l10n/messages.properties | 2 - .../resources/l10n/messages_ar.properties | 3 - .../resources/l10n/messages_cs.properties | 3 - .../resources/l10n/messages_de.properties | 3 - .../resources/l10n/messages_es.properties | 3 - .../resources/l10n/messages_fr.properties | 3 - .../resources/l10n/messages_it.properties | 3 - .../resources/l10n/messages_ja.properties | 3 - .../resources/l10n/messages_nl.properties | 3 - .../resources/l10n/messages_pl.properties | 3 - .../resources/l10n/messages_pt.properties | 3 - .../resources/l10n/messages_ru.properties | 3 - .../resources/l10n/messages_uk_UA.properties | 2 - .../resources/l10n/messages_zh_CN.properties | 3 - .../configdialog/FreeformFinSetConfig.java | 60 ++++++++++++++++--- 15 files changed, 52 insertions(+), 48 deletions(-) diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 840d1e5c2..3cb057103 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -1342,8 +1342,6 @@ 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 table entry 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 diff --git a/core/src/main/resources/l10n/messages_ar.properties b/core/src/main/resources/l10n/messages_ar.properties index 609c91090..92a7eee9a 100644 --- a/core/src/main/resources/l10n/messages_ar.properties +++ b/core/src/main/resources/l10n/messages_ar.properties @@ -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 = حَجِّمْ الزعنفة diff --git a/core/src/main/resources/l10n/messages_cs.properties b/core/src/main/resources/l10n/messages_cs.properties index 28b602379..af6f08cbe 100644 --- a/core/src/main/resources/l10n/messages_cs.properties +++ b/core/src/main/resources/l10n/messages_cs.properties @@ -715,9 +715,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Pozice vzhledem k: FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.FincrossSection = Hrany stabiliztoru: 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 thnout: Pridej a presun body FreeformFinSetConfig.lbl.ctrlClick = Ctrl+klik: Odstran bod FreeformFinSetConfig.lbl.scaleFin = Mertko stabiliztoru diff --git a/core/src/main/resources/l10n/messages_de.properties b/core/src/main/resources/l10n/messages_de.properties index 9544e29a4..446f9f52b 100644 --- a/core/src/main/resources/l10n/messages_de.properties +++ b/core/src/main/resources/l10n/messages_de.properties @@ -772,9 +772,6 @@ FreeformFinSetCfg.lbl.Posrelativeto = Position relativ zu: FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.FincrossSection = Querschnitt: FreeformFinSetCfg.lbl.Thickness = Wandstrke: -! doubleClick1 + 2 form the message "Doppelklick zum Bearbeiten", ungefhr in der Mitte teilen -FreeformFinSetConfig.lbl.doubleClick1 = Doppelklick -FreeformFinSetConfig.lbl.doubleClick2 = zum Bearbeiten FreeformFinSetConfig.lbl.clickDrag = Klicken+Ziehen: Punkte bewegen und hinzufgen FreeformFinSetConfig.lbl.ctrlClick = Strg+Klick: Punkt lschen FreeformFinSetConfig.lbl.scaleFin = Leitwerk skalieren diff --git a/core/src/main/resources/l10n/messages_es.properties b/core/src/main/resources/l10n/messages_es.properties index 154d5a816..f4197626e 100644 --- a/core/src/main/resources/l10n/messages_es.properties +++ b/core/src/main/resources/l10n/messages_es.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_fr.properties b/core/src/main/resources/l10n/messages_fr.properties index 98ca53516..fbe18ed00 100644 --- a/core/src/main/resources/l10n/messages_fr.properties +++ b/core/src/main/resources/l10n/messages_fr.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_it.properties b/core/src/main/resources/l10n/messages_it.properties index 19f4f4715..3e6786b2b 100644 --- a/core/src/main/resources/l10n/messages_it.properties +++ b/core/src/main/resources/l10n/messages_it.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_ja.properties b/core/src/main/resources/l10n/messages_ja.properties index 86e9a7789..7671fa434 100644 --- a/core/src/main/resources/l10n/messages_ja.properties +++ b/core/src/main/resources/l10n/messages_ja.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_nl.properties b/core/src/main/resources/l10n/messages_nl.properties index ecd1496c5..06bb2d5d6 100644 --- a/core/src/main/resources/l10n/messages_nl.properties +++ b/core/src/main/resources/l10n/messages_nl.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_pl.properties b/core/src/main/resources/l10n/messages_pl.properties index d46226ee5..69c80af8e 100644 --- a/core/src/main/resources/l10n/messages_pl.properties +++ b/core/src/main/resources/l10n/messages_pl.properties @@ -719,9 +719,6 @@ ComponentInfo.EngineBlock = Blokada silnika unieruchamia silnik wewn\u01 FreeformFinSetCfg.lbl.plus = plus FreeformFinSetCfg.lbl.FincrossSection = Przekrj 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 diff --git a/core/src/main/resources/l10n/messages_pt.properties b/core/src/main/resources/l10n/messages_pt.properties index c71236ce9..7dd1875d4 100644 --- a/core/src/main/resources/l10n/messages_pt.properties +++ b/core/src/main/resources/l10n/messages_pt.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_ru.properties b/core/src/main/resources/l10n/messages_ru.properties index 03e9250f1..a90dac2cb 100644 --- a/core/src/main/resources/l10n/messages_ru.properties +++ b/core/src/main/resources/l10n/messages_ru.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_uk_UA.properties b/core/src/main/resources/l10n/messages_uk_UA.properties index c3e5770ec..9038f46ae 100644 --- a/core/src/main/resources/l10n/messages_uk_UA.properties +++ b/core/src/main/resources/l10n/messages_uk_UA.properties @@ -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 diff --git a/core/src/main/resources/l10n/messages_zh_CN.properties b/core/src/main/resources/l10n/messages_zh_CN.properties index f9903be6f..8275be969 100644 --- a/core/src/main/resources/l10n/messages_zh_CN.properties +++ b/core/src/main/resources/l10n/messages_zh_CN.properties @@ -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 diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java index c3f24acf0..96ad6d734 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java @@ -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,8 @@ 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 2"); - panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "spanx 2, wrap"); - panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 2"); + 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"); @@ -503,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); @@ -681,8 +726,8 @@ public class FreeformFinSetConfig extends FinSetConfig { @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 @@ -695,8 +740,7 @@ public class FreeformFinSetConfig extends FinSetConfig { return; } } - - super.mouseClicked(event); + super.mouseClicked(event); } private int getPoint(MouseEvent event) { From 1ee09f285043ab36629ff827546263e25c575526 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Oct 2024 16:22:15 +0200 Subject: [PATCH 13/16] Use proper snap highlight colors for different themes --- .../swing/gui/scalefigure/FinPointFigure.java | 4 +++- .../openrocket/swing/gui/theme/UITheme.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java index 4f3911ab7..656bde9ee 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java +++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java @@ -68,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(); @@ -94,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 @@ -277,7 +279,7 @@ public class FinPointFigure extends AbstractScaleFigure { final float highlightWidth_m = (float) (LINE_WIDTH_HIGHLIGHT_PIXELS / scale ); g2.setStroke(new BasicStroke(highlightWidth_m)); - g2.setColor(Color.RED); + g2.setColor(finPointSnapHighlightColor); g2.draw(new Line2D.Double(start.x, start.y, end.x, end.y)); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java b/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java index a64bd1865..5706a0273 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java +++ b/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java @@ -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(); From 3ac5953b94baeb609fcbc58fc1e2f96f516f6bfa Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 12 Oct 2024 16:25:18 +0200 Subject: [PATCH 14/16] Code cleanup --- .../configdialog/FreeformFinSetConfig.java | 166 ++++++++---------- 1 file changed, 77 insertions(+), 89 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java index 96ad6d734..f29dc4e48 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java @@ -590,44 +590,14 @@ public class FreeformFinSetConfig extends FinSetConfig { Point2D.Double point = getCoordinates(event); final FreeformFinSet finset = (FreeformFinSet) component; - // If shift is held down and a point is being dragged, constrain angle relative to previous or following point - int lockIndex = -1; + // If shift is held down, apply snapping int highlightIndex = -1; if ((mods & MouseEvent.SHIFT_DOWN_MASK) != 0) { - if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) { - if (dragIndex < finset.getFinPoints().length-1) { - lockIndex = dragIndex + 1; - highlightIndex = dragIndex; - } - } - else if (dragIndex > 0) { - lockIndex = dragIndex - 1; - highlightIndex = dragIndex - 1; - } + int lockIndex = getLockIndex(mods); + highlightIndex = getHighlightIndex(lockIndex); if (lockIndex >= 0) { - // Fetch point to lock to - final Coordinate lockPoint = finset.getFinPoints()[lockIndex]; - // Distances to vertical and horizontal lines - final double diffX = point.x - lockPoint.x; - final double diffY = point.y - lockPoint.y; - final double distanceX = Math.abs(diffX); - final double distanceY = Math.abs(diffY); - // Calculate distance to 45 or 135 degree line, as appropriate - final double a = 1; // always - final double b = (Math.signum(diffX) == Math.signum(diffY)) ? -1 : 1; - final double c = -(a*lockPoint.x + b*lockPoint.y); - final double distanceDiag = Math.abs(a*point.x + b*point.y + c) / Math.sqrt(2); - - // Snap in the appropriate direction - if (distanceX <= distanceY && distanceX <= distanceDiag) // snap horizontal - point.x = lockPoint.x; - else if (distanceY <= distanceX && distanceY <= distanceDiag) // snap vertical - point.y = lockPoint.y; - else { // snap diagonal - point.x = (b*( b*point.x - a*point.y) - a*c) / 2; - point.y = (a*(-b*point.x + a*point.y) - b*c) / 2; - } + point = snapPoint(point, finset.getFinPoints()[lockIndex]); } } figure.setHighlightIndex(highlightIndex); @@ -643,29 +613,59 @@ 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(dragRectangle.height -figure.getBorderHeight())) { - hitBorder = true; - dragRectangle.y += dragPoint.y - (dragRectangle.height - figure.getBorderHeight()); - } + private int getLockIndex(int mods) { + if ((mods & MouseEvent.CTRL_DOWN_MASK) != 0) { + return (dragIndex < ((FreeformFinSet) component).getFinPoints().length - 1) ? dragIndex + 1 : -1; + } else { + return (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(); @@ -675,43 +675,31 @@ 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(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(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 From aeeddcd921c02a81d4af8159cb3b57e077155e86 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 13 Oct 2024 11:23:07 +0200 Subject: [PATCH 15/16] Improve documentation --- .../openrocket/swing/gui/scalefigure/FinPointFigure.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java index 656bde9ee..0325a9406 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java +++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java @@ -60,7 +60,7 @@ public class FinPointFigure extends AbstractScaleFigure { private Rectangle2D.Double[] finPointHandles = null; private int selectedIndex = -1; - private int highlightIndex = -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; @@ -267,6 +267,10 @@ public class FinPointFigure extends AbstractScaleFigure { 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(); From 9288b93383b9cf99c19a7d7ae5e47e85b0aa94f3 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 13 Oct 2024 11:23:17 +0200 Subject: [PATCH 16/16] Disable snapping for root points --- .../gui/configdialog/FreeformFinSetConfig.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java index f29dc4e48..59e41b4a3 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/FreeformFinSetConfig.java @@ -591,16 +591,15 @@ public class FreeformFinSetConfig extends FinSetConfig { final FreeformFinSet finset = (FreeformFinSet) component; // If shift is held down, apply snapping - int highlightIndex = -1; if ((mods & MouseEvent.SHIFT_DOWN_MASK) != 0) { int lockIndex = getLockIndex(mods); - highlightIndex = getHighlightIndex(lockIndex); - if (lockIndex >= 0) { + if (lockIndex != -1) { point = snapPoint(point, finset.getFinPoints()[lockIndex]); + int highlightIndex = getHighlightIndex(lockIndex); + figure.setHighlightIndex(highlightIndex); } } - figure.setHighlightIndex(highlightIndex); try { finset.setPoint(dragIndex, point.x, point.y); @@ -617,11 +616,17 @@ public class FreeformFinSetConfig extends FinSetConfig { handleScrolling(); } + /** + * 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 < ((FreeformFinSet) component).getFinPoints().length - 1) ? dragIndex + 1 : -1; + return (dragIndex > 0 && dragIndex < length - 1) ? dragIndex + 1 : -1; } else { - return (dragIndex > 0) ? dragIndex - 1 : -1; + return (dragIndex < length - 1 && dragIndex > 0) ? dragIndex - 1 : -1; } }