From c7b3466dd542f0ba48134a5e13f244b2bb054996 Mon Sep 17 00:00:00 2001 From: Joe Pfeiffer Date: Sat, 29 Sep 2018 16:17:33 -0600 Subject: [PATCH 1/2] Unstable (#5) * Fix Average Thrust Calculation (fixes issue #441) Remove test for short time interval before first data point in thrust curve. Comment said it was for numerical stability; multiplying by a small number and then adding doesn't introduce any instabilities I'm aware of in this code. Add parentheses to clarify that values are being multiplied by time intervals, not divided. * Code clarification; should make no difference to results * [refactor] fixed warnings and made variable names more explicit in [Freeform]FinSetConfig Dialogs - de-duped writeCSVFile methods * [Fixes #424] Respaced FinSetConfig Window: Resolved some sources of phantom whitespace; Spacing on component configuration windows is now generally tighter. * [cleanup] Refactored naming in ScaleSelector to be more consistent 'Zoom' -> 'Scale' * [refactor] updated BoundingBox class to be more useful - FlightConfiguration now exposes the BoundingBox method for its rocket * [refactor] Reduce redundant methods in Scalefigures, and harmonize common function names - removed interface that was only inherited by the single AbstractBaseClass - harmonizes the border pixels variables in the scalefigure package * [rm] excised EXTRA_SCALE (==S) factor in ScaleFigure Code * [fix] FinPointFigure now auto-scales correctly - auto-zooms on startup - ScaleSelector Text updates with +/- buttons - adjusts fin-point drawing code * [feat] FinPointFigure draws its parent/mounting half-body (w/front & back terminators) * [fixes #425] FinPointFigure ScrollBars now adjust with zoom in/out * [fix] clicking away from points now longer causes an exception * Version Bump to Alpha 8 * [fixes #424] Addes back in ConfigDialog outside spacing. * [feature][Resolves #426] implemented FinPoint SelectedIndex Indicators - figure and table update each other * [fixes #419] Clicking in fin-point figure now calculates closest segment correctly * [fixes #431] Fins default to instance count / fin count == 1 - Fixed init bug - added unittests for fin count loading/saving/creation * [fix] Revert patch 6289aef0... which introduced simulation anomalies * [resolves #423][partial] BarrowmanCalculator no longer multiplies instanced leaf nodes. * [fix] Fixes the way BarrowmanCalculator handles instancing, particularly for ComponentAssemblies * [test] Moved fins from core-body to booster-body; (they are now doubly-instanced); adjusted tests to accept this. * [refactor] renamed FinSet#fins => FinSet#finCount to make it's meaning more explicit * [minor][debug][oneline] removed excess sys.err debug line * [fixes #439] May now delete points again, in the FreeformFinSetConfig window * [fix] AbstractScaleFigure now stores (& requires!) the visible bounds when setting zoom/scale. - if the visible bounds are larger than the requested scale bounds, then the figure is expanded to match. * [fixes #436] Rocket figures now center as desired. * [fixes #425][fixes #440] FinPointFigure contents are bottom-aligned, properly sized. * [refactor] separated FinSet Tests into files corresponding to FinSet, TrapezoidalFinSet, and FreeformFinSet * [fixes #419] Adding new points to FreeformFins are now placed at the mouse cursor * Little bit more massaging for clarity (replace avgImpulse with impulse) * [fixes #426] reworks FreeformFinSet Selected point display... it is now a second, expanded, different colored box. * missed reversing the operands in the calculation of last bit of impulse * [build] Updated dependencies for running from intellij * [feat] added shared build configurations for Intellij at .idea/runConfigurations/*" * [fix][config] rename Run Target Configurations * [build] added jar artifact for IDEA Intellij build * [fix] run configuration and jar paths are now cross-platform * [fix] may now create and drag a point in one click. * [fix] cleanup up unused imports in core/test/net/sf/openrocket/rocketcomponent/* * [cleanup] removed dead code, and fixed javadocs * [refactor] tightened access specifiers in FinSet.java * [fix][Refactor] rocketcomponent.position.RadiusMethod to be clearer 1. renamed getOuterRadius() => getBoundingRadius() Previous function did not make sense, where implemented in FinSet. 2. Changed implementation of RadiusMethod.*.getRadius() now fails a bit more gracefully. * [fix] Fixes repeated bug in Presets/Material Loading -- inconsistent test criteria * Closes 443 When the events STAGE_SEPARATION and EJECTION_CHARGE are both triggered by BURNOUT, both events occur simultaneously and either can be inserted in EventQueue first. If STAGE_SEPARATION is inserted first, the filter in BasicEventSimulationEngine.java ignoring events from components that are no longer attached to the rocket drops ignores EJECTION_CHARGE. If second stage IGNITION is triggered by EJECTION_CHARGE it is filtered out, and second stage IGNITION fails to happen. This can be seen in the following snippet of a log file: 10592 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=BURNOUT,time=1.03,source=Body tube,data=B4] 10592 DEBUG [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - detected Motor Burnout for motor B4@ 1.03 on stage 1: Stage 10592 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=STAGE_SEPARATION,time=1.03,source=Stage,data=null] 10592 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=EJECTION_CHARGE,time=1.0311458852237796,source=Stage,data=B4], FlightEvent[type=ALTITUDE,time=1.0311458852237796,source=Rocket,data=[25.502739793351193;25.603323566419885]]] 10592 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=STAGE_SEPARATION,time=1.03,source=Stage,data=null] 10592 INFO [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - ==>> @ 1.03115; from Branch: Sustainer ---- Branching: Stage ---- 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=EJECTION_CHARGE,time=1.0311458852237796,source=Stage,data=B4] 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=ALTITUDE,time=1.0311458852237796,source=Rocket,data=[25.502739793351193;25.603323566419885]]] 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Ignoring event from unattached componenent 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=ALTITUDE,time=1.0311458852237796,source=Rocket,data=[25.502739793351193;25.603323566419885]] 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [] 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=ALTITUDE,time=1.0311458852237796,source=Rocket,data=[25.502739793351193;25.603323566419885]] 10593 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Taking simulation step at t=1.0311458852237796 altitude 25.603323566419885 10593 TRACE [pool-4-thread-1] n.s.o.s.RK4SimulationStepper - Too small time step 0.0014030377018961126 (limiting factor 5), using 0.0025 instead. 10593 TRACE [pool-4-thread-1] n.s.o.s.RK4SimulationStepper - Thrust = 0.0 Note here that there was no IGNITION in the sustainer branch, and the Thrust is 0.0 at the end of the snippet. Moving the test for ignition events ahead of the filter assures the IGNITION is scheduled, as seen in this log file snippet: 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=BURNOUT,time=1.03,source=Body tube,data=B4] 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]]] 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=BURNOUT,time=1.03,source=Body tube,data=B4] 8994 DEBUG [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - detected Motor Burnout for motor B4@ 1.03 on stage 1: Stage 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=STAGE_SEPARATION,time=1.03,source=Stage,data=null] 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=EJECTION_CHARGE,time=1.0302951181945657,source=Stage,data=B4], FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]]] 8994 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=STAGE_SEPARATION,time=1.03,source=Stage,data=null] 8995 INFO [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - ==>> @ 1.03030; from Branch: Sustainer ---- Branching: Stage ---- 8995 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=EJECTION_CHARGE,time=1.0302951181945657,source=Stage,data=B4] 8995 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]]] 8995 INFO [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Queueing Ignition Event for: Body tube/334ebb79 / A8 - Armed @: 1.0302951181945657 8995 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Ignoring event from unattached componenent 8995 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=IGNITION,time=1.0302951181945657,source=Body tube,data=A8] 8995 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]]] 8996 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=IGNITION,time=1.0302951181945657,source=Body tube,data=A8] 8996 INFO [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Igniting motor: Body tube/334ebb79 / A8 - Armed @1.0302951181945657 8996 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Obtained event from queue: FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]] 8996 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Remaining EventQueue = [FlightEvent[type=BURNOUT,time=1.7602951181945656,source=Body tube,data=A8]] 8996 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Handling event FlightEvent[type=ALTITUDE,time=1.0302951181945657,source=Rocket,data=[25.478255057184594;25.5788943164009]] 8996 TRACE [pool-4-thread-1] n.s.o.s.BasicEventSimulationEngine - Taking simulation step at t=1.0302951181945657 altitude 25.5788943164009 8996 TRACE [pool-4-thread-1] n.s.o.s.RK4SimulationStepper - Too small time step 0.0012514398786730699 (limiting factor 5), using 0.0025 instead. 8996 TRACE [pool-4-thread-1] n.s.o.s.RK4SimulationStepper - Thrust = 0.015609756097560644 Here, the IGNITION does take place, and Thrust is non-zero. Displaying a plot of the flight, and saving CSV files, shows a normal two-stage flight profile. This commit does two things: (1) adds a little more logging, in particular logging what event has been obtained from EventQueue and logging when that event is ignored. (2) moves the motor ignition events test ahead of the filter, as described above. * Correct active stages after STAGE_SEPARATION event * oops, didn't want to keep the extra debugging log entries * more fixes to stage ignition: now also pays attention to ignition type and additional delay --- .idea/artifacts/openrocket_jar.xml | 39 ++ .idea/runConfigurations/All_tests.xml | 25 + .idea/runConfigurations/Openrocket_UI_Jar.xml | 13 + core/OpenRocket Core.iml | 13 +- core/resources/build.properties | 2 +- core/src/META-INF/MANIFEST.MF | 3 + .../aerodynamics/AerodynamicForces.java | 39 +- .../aerodynamics/BarrowmanCalculator.java | 121 ++-- .../aerodynamics/barrowman/FinSetCalc.java | 4 +- .../sf/openrocket/motor/ThrustCurveMotor.java | 31 +- .../rocketcomponent/ComponentAssembly.java | 4 +- .../sf/openrocket/rocketcomponent/FinSet.java | 58 +- .../rocketcomponent/FlightConfiguration.java | 25 +- .../rocketcomponent/FreeformFinSet.java | 35 +- .../rocketcomponent/TrapezoidFinSet.java | 2 +- .../position/RadiusMethod.java | 22 +- .../position/RadiusPositionable.java | 6 +- .../BasicEventSimulationEngine.java | 17 +- .../simulation/SimulationStatus.java | 4 +- .../net/sf/openrocket/util/BoundingBox.java | 115 +++- .../net/sf/openrocket/util/TestRockets.java | 30 +- .../aerodynamics/BarrowmanCalculatorTest.java | 10 +- .../aerodynamics/FinSetCalcTest.java | 4 +- .../masscalc/MassCalculatorTest.java | 149 ++--- .../preset/BodyTubePresetTests.java | 4 +- .../preset/BulkHeadPresetTests.java | 4 +- .../preset/CenteringRingPresetTests.java | 4 +- .../preset/EngineBlockPresetTests.java | 4 +- .../preset/LaunchLugPresetTests.java | 4 +- .../preset/NoseConePresetTests.java | 4 +- .../preset/TransitionPresetTests.java | 4 +- .../preset/TubeCouplerPresetTests.java | 4 +- .../rocketcomponent/FinSetTest.java | 374 +----------- .../rocketcomponent/FreeformFinSetTest.java | 299 +++++++++ .../rocketcomponent/ParallelStageTest.java | 50 +- .../rocketcomponent/RocketTest.java | 24 +- .../rocketcomponent/TrapezoidFinSetTest.java | 123 ++++ lib-test/OpenRocket Test Libraries.iml | 4 +- swing/OpenRocket Swing.iml | 74 ++- .../gui/configdialog/FinSetConfig.java | 36 +- .../configdialog/FreeformFinSetConfig.java | 334 +++++------ .../configdialog/RocketComponentConfig.java | 10 +- .../GeneralOptimizationDialog.java | 1 - .../sf/openrocket/gui/print/DesignReport.java | 6 +- .../sf/openrocket/gui/print/PrintFigure.java | 8 +- .../gui/rocketfigure/FinSetShapes.java | 16 +- .../gui/rocketfigure/MassComponentShapes.java | 5 +- .../gui/rocketfigure/MassObjectShapes.java | 6 +- .../gui/rocketfigure/ParachuteShapes.java | 5 +- .../gui/rocketfigure/RailButtonShapes.java | 32 +- .../rocketfigure/RocketComponentShape.java | 2 - .../gui/rocketfigure/ShockCordShapes.java | 8 +- .../gui/rocketfigure/StreamerShapes.java | 12 +- .../SymmetricComponentShapes.java | 20 +- .../gui/rocketfigure/TransitionShapes.java | 26 +- .../gui/rocketfigure/TubeFinSetShapes.java | 4 +- .../gui/rocketfigure/TubeShapes.java | 10 +- .../gui/scalefigure/AbstractScaleFigure.java | 240 +++++--- .../gui/scalefigure/FinPointFigure.java | 567 ++++++++++-------- .../gui/scalefigure/RocketFigure.java | 356 ++++------- .../gui/scalefigure/RocketPanel.java | 4 +- .../gui/scalefigure/ScaleFigure.java | 82 --- .../gui/scalefigure/ScaleScrollPane.java | 221 +++---- .../gui/scalefigure/ScaleSelector.java | 94 +-- 64 files changed, 2018 insertions(+), 1838 deletions(-) create mode 100644 .idea/artifacts/openrocket_jar.xml create mode 100644 .idea/runConfigurations/All_tests.xml create mode 100644 .idea/runConfigurations/Openrocket_UI_Jar.xml create mode 100644 core/src/META-INF/MANIFEST.MF create mode 100644 core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java create mode 100644 core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java delete mode 100644 swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java diff --git a/.idea/artifacts/openrocket_jar.xml b/.idea/artifacts/openrocket_jar.xml new file mode 100644 index 000000000..9bbc47070 --- /dev/null +++ b/.idea/artifacts/openrocket_jar.xml @@ -0,0 +1,39 @@ + + + $PROJECT_DIR$/build/jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/All_tests.xml b/.idea/runConfigurations/All_tests.xml new file mode 100644 index 000000000..26cc40265 --- /dev/null +++ b/.idea/runConfigurations/All_tests.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Openrocket_UI_Jar.xml b/.idea/runConfigurations/Openrocket_UI_Jar.xml new file mode 100644 index 000000000..f5e1da03e --- /dev/null +++ b/.idea/runConfigurations/Openrocket_UI_Jar.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/core/OpenRocket Core.iml b/core/OpenRocket Core.iml index e1e2c5ccd..eca58a704 100644 --- a/core/OpenRocket Core.iml +++ b/core/OpenRocket Core.iml @@ -23,13 +23,13 @@ - + - + @@ -177,14 +177,5 @@ - - - - - - - - - \ No newline at end of file diff --git a/core/resources/build.properties b/core/resources/build.properties index 6b753a3ae..806d5a7ec 100644 --- a/core/resources/build.properties +++ b/core/resources/build.properties @@ -1,7 +1,7 @@ # The OpenRocket build version -build.version=alpha5 +build.version=alpha8 # The source of the package. When building a package for a specific diff --git a/core/src/META-INF/MANIFEST.MF b/core/src/META-INF/MANIFEST.MF new file mode 100644 index 000000000..e9cec3261 --- /dev/null +++ b/core/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: net.sf.openrocket.startup.SwingStartup + diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java index cbfc3ddb7..da41651be 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java @@ -286,7 +286,7 @@ public class AerodynamicForces implements Cloneable, Monitorable { /** * Zero all values to 0 / Coordinate.NUL. Component is left as it was. */ - public void zero() { + public AerodynamicForces zero() { // component untouched setAxisymmetric(true); @@ -303,6 +303,8 @@ public class AerodynamicForces implements Cloneable, Monitorable { setCD(0); setPitchDampingMoment(0); setYawDampingMoment(0); + + return this; } @@ -388,4 +390,39 @@ public class AerodynamicForces implements Cloneable, Monitorable { public int getModID() { return modID; } + + public AerodynamicForces merge(AerodynamicForces other) { + + this.cp = cp.average(other.getCP()); + this.CNa = CNa + other.getCNa(); + this.CN = CN + other.getCN(); + this.Cm = Cm + other.getCm(); + this.Cside = Cside + other.getCside(); + this.Cyaw = Cyaw + other.getCyaw(); + this.Croll = Croll + other.getCroll(); + this.CrollDamp = CrollDamp + other.getCrollDamp(); + this.CrollForce = CrollForce + other.getCrollForce(); + + modID++; + + return this; + } + + public AerodynamicForces multiplex(final int instanceCount) { + + this.cp = cp.setWeight(cp.weight*instanceCount); + this.CNa = CNa*instanceCount; + this.CN = CN*instanceCount; + this.Cm = Cm*instanceCount; + this.Cside = Cside*instanceCount; + this.Cyaw = Cyaw*instanceCount; + this.Croll = Croll*instanceCount; + this.CrollDamp = CrollDamp*instanceCount; + this.CrollForce = CrollForce*instanceCount; + + modID++; + + return this; + } + } diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index cd5d07759..ad79ec07d 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -158,22 +158,16 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { * Perform the actual CP calculation. */ private AerodynamicForces calculateNonAxialForces(FlightConfiguration configuration, FlightConditions conditions, - Map map, WarningSet warnings) { + Map calculators, WarningSet warnings) { checkCache(configuration); - AerodynamicForces total = new AerodynamicForces(); - total.zero(); - - AerodynamicForces forces = new AerodynamicForces(); - if (warnings == null) warnings = ignoreWarningSet; if (conditions.getAOA() > 17.5 * Math.PI / 180) warnings.add(new Warning.LargeAOA(conditions.getAOA())); - if (calcMap == null) buildCalcMap(configuration); @@ -182,73 +176,60 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { warnings.add( Warning.DIAMETER_DISCONTINUITY); } - for (RocketComponent component : configuration.getActiveComponents()) { - - // Skip non-aerodynamic components - if (!component.isAerodynamic()) - continue; - - - // Call calculation method - forces.zero(); - RocketComponentCalc calcObj = calcMap.get(component); - calcObj.calculateNonaxialForces(conditions, forces, warnings); - - -// // to account for non axi-symmetric rockets such as a Delta-IV heavy, or a Falcon-9 Heavy -// if(( ! component.isAxisymmetric()) &&( component instanceof RingInstanceable )){ -// RingInstanceable ring = (RingInstanceable)component; -// forces.setAxisymmetric(false); -// total.setAxisymmetric(false); -// -// // TODO : Implement Best-Case, Worst-Case Cp calculations -// double minAngle = ring.getAngularOffset(); // angle of minimum CP, MOI -// double maxAngle = minAngle+Math.PI/2; // angle of maximum CP, MOI -// -// // worst case: ignore the CP contribution from *twin* externals -// // NYI -// -// // best case: the twins contribute their full CP broadside -// // NYI -// -// } - - int instanceCount = component.getLocations().length; - Coordinate x_cp_comp = forces.getCP(); - Coordinate x_cp_weighted = x_cp_comp.setWeight(x_cp_comp.weight * instanceCount); - Coordinate x_cp_absolute = component.toAbsolute(x_cp_weighted)[0]; - forces.setCP(x_cp_absolute); - double CN_instanced = forces.getCN() * instanceCount; - forces.setCm(CN_instanced * forces.getCP().x / conditions.getRefLength()); - - if (map != null) { - AerodynamicForces f = map.get(component); - - f.setCP(forces.getCP()); - f.setCNa(forces.getCNa()); - f.setCN(forces.getCN()); - f.setCm(forces.getCm()); - f.setCside(forces.getCside()); - f.setCyaw(forces.getCyaw()); - f.setCroll(forces.getCroll()); - f.setCrollDamp(forces.getCrollDamp()); - f.setCrollForce(forces.getCrollForce()); - } - - total.setCP(total.getCP().average(forces.getCP())); - total.setCNa(total.getCNa() + forces.getCNa()); - total.setCN(total.getCN() + forces.getCN()); - total.setCm(total.getCm() + forces.getCm()); - total.setCside(total.getCside() + forces.getCside()); - total.setCyaw(total.getCyaw() + forces.getCyaw()); - total.setCroll(total.getCroll() + forces.getCroll()); - total.setCrollDamp(total.getCrollDamp() + forces.getCrollDamp()); - total.setCrollForce(total.getCrollForce() + forces.getCrollForce()); - } + AerodynamicForces total = calculateAssemblyNonAxialForces(configuration.getRocket(), configuration, conditions, calculators, warnings, ""); return total; } + private AerodynamicForces calculateAssemblyNonAxialForces( final RocketComponent component, + FlightConfiguration configuration, FlightConditions conditions, + Map calculators, WarningSet warnings, + String indent) { + + final AerodynamicForces assemblyForces= new AerodynamicForces().zero(); + +// System.err.println(String.format("%s@@ %s <%s>", indent, component.getName(), component.getClass().getSimpleName())); + + // ==== calculate child forces ==== + for (RocketComponent child: component.getChildren()) { + AerodynamicForces childForces = calculateAssemblyNonAxialForces( child, configuration, conditions, calculators, warnings, indent+" "); + assemblyForces.merge(childForces); + } + + // calculate *this* component's forces + RocketComponentCalc calcObj = calcMap.get(component); + if(null != calcObj) { + AerodynamicForces componentForces = new AerodynamicForces().zero(); + calcObj.calculateNonaxialForces(conditions, componentForces, warnings); + + Coordinate x_cp_comp = componentForces.getCP(); + Coordinate x_cp_weighted = x_cp_comp.setWeight(x_cp_comp.weight); + Coordinate x_cp_absolute = component.toAbsolute(x_cp_weighted)[0]; + componentForces.setCP(x_cp_absolute); + double CN_instanced = componentForces.getCN(); + componentForces.setCm(CN_instanced * componentForces.getCP().x / conditions.getRefLength()); + +// if( 0.0001 < Math.abs(0 - componentForces.getCNa())){ +// System.err.println(String.format("%s....Component.CNa: %g @ CPx: %g", indent, componentForces.getCNa(), componentForces.getCP().x)); +// } + + assemblyForces.merge(componentForces); + } + +// if( 0.0001 < Math.abs(0 - assemblyForces.getCNa())){ +// System.err.println(String.format("%s....Assembly.CNa: %g @ CPx: %g", indent, assemblyForces.getCNa(), assemblyForces.getCP().x)); +// } + + // fetches instanced versions + // int instanceCount = component.getLocations().length; + + if( component.allowsChildren() && (component.getInstanceCount() > 1)) { + return assemblyForces.multiplex(component.getInstanceCount()); + }else { + return assemblyForces; + } + } + @Override public boolean isContinuous( final Rocket rkt){ diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 156eea790..36b065ac6 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -77,7 +77,7 @@ public class FinSetCalc extends RocketComponentCalc { } /* - * Calculates the non-axial forces produced by *one* *instance* of the fins. + * Calculates the non-axial forces produced by each set of fins. * (normal and side forces, pitch, yaw and roll moments, CP position, CNa). */ @Override @@ -124,7 +124,7 @@ public class FinSetCalc extends RocketComponentCalc { cna = cna1 * mul; } else { // Basic CNa assuming full efficiency - cna = cna1 / 2.0; + cna = cna1 * finCount / 2.0; } // logger.debug("Component cna = {}", cna); diff --git a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java index ac4b37a6b..3e851f260 100644 --- a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java +++ b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -313,34 +313,31 @@ public class ThrustCurveMotor implements Motor, Comparable, Se if ( endTime <= time[timeIndex+1] ) { // we are completely within this time slice so the computation of the average is pretty easy: - double avgImpulse = MathUtil.map(startTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); - avgImpulse += MathUtil.map(endTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); - avgImpulse /= 2.0; - return avgImpulse; - } - - // portion from startTime through time[timeIndex] - double avgImpulse = 0.0; - // For numeric stability. - if( time[timeIndex+1] - startTime > 0.001 ) { - avgImpulse = (MathUtil.map(startTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]) + thrust[timeIndex+1]) - / 2.0 * (time[timeIndex+1] - startTime); + double startThrust = MathUtil.map(startTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); + double endThrust = MathUtil.map(endTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); + return (startThrust + endThrust) / 2.0; } + + double impulse = 0.0; + + // portion from startTime through time[timeIndex+1] + double startThrust = MathUtil.map(startTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); + impulse = (time[timeIndex+1] - startTime) * (startThrust + thrust[timeIndex+1]) / 2.0; // Now add the whole steps; timeIndex++; - while( timeIndex < time.length -1 && endTime >= time[timeIndex+1] ) { - avgImpulse += (thrust[timeIndex] + thrust[timeIndex+1]) / 2.0 * (time[timeIndex+1]-time[timeIndex]); + while ( timeIndex < time.length -1 && endTime >= time[timeIndex+1] ) { + impulse += (time[timeIndex+1] - time[timeIndex]) * (thrust[timeIndex] + thrust[timeIndex+1]) / 2.0; timeIndex++; } // Now add the bit after the last time index if ( timeIndex < time.length -1 ) { - double endInstImpulse = MathUtil.map( endTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); - avgImpulse += (thrust[timeIndex] + endInstImpulse) / 2.0 * (endTime - time[timeIndex]); + double endThrust = MathUtil.map( endTime, time[timeIndex], time[timeIndex+1], thrust[timeIndex], thrust[timeIndex+1]); + impulse += (endTime - time[timeIndex]) * (thrust[timeIndex] + endThrust) / 2.0; } - return avgImpulse / (endTime - startTime); + return impulse / (endTime - startTime); } @Override diff --git a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java index 16c40b0ac..75d688849 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java +++ b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java @@ -87,8 +87,8 @@ public abstract class ComponentAssembly extends RocketComponent implements Axia public double getRotationalUnitInertia() { return 0; } - - public double getOuterRadius(){ + + public double getBoundingRadius(){ double outerRadius=0; for( RocketComponent comp : children ){ double thisRadius=0; diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index 465e459a0..d5387aebf 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -74,12 +74,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab /** * Number of fins. */ - protected int fins = 3; + private int finCount = 1; /** * Rotation about the x-axis by 2*PI/fins. */ - protected Transformation finRotation = Transformation.IDENTITY; + private Transformation finRotation = Transformation.IDENTITY; @@ -87,12 +87,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab * Rotation angle of the first fin. Zero corresponds to the positive y-axis. */ private AngleMethod angleMethod = AngleMethod.RELATIVE; - protected double firstFinOffset = 0; + private double firstFinOffset = 0; /** * Cant angle of fins. */ - protected double cantAngle = 0; + private double cantAngle = 0; /* Cached value: */ private Transformation cantRotation = null; @@ -109,7 +109,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab /** * The cross-section shape of the fins. */ - protected CrossSection crossSection = CrossSection.SQUARE; + private CrossSection crossSection = CrossSection.SQUARE; /* @@ -124,10 +124,10 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab * Fin fillet properties */ - protected Material filletMaterial = null; - protected double filletRadius = 0; - protected double filletCenterY = 0; - + private Material filletMaterial; + private double filletRadius = 0; + private double filletCenterY = 0; + // Cached fin area & CG. Validity of both must be checked using finArea! // Fin area does not include fin tabs, CG does. private double finArea = -1; @@ -155,7 +155,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab * @return The number of fins. */ public int getFinCount() { - return fins; + return finCount; } /** @@ -163,13 +163,13 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab * @param n The number of fins, greater of equal to one. */ public void setFinCount(int n) { - if (fins == n) + if (finCount == n) return; if (n < 1) n = 1; if (n > 8) n = 8; - fins = n; + finCount = n; fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } @@ -316,7 +316,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab /** * Return the tab front edge position from the front of the fin. */ - public double getTabFrontEdge() { + private double getTabFrontEdge() { switch (this.tabRelativePosition) { case FRONT: return tabShift; @@ -335,7 +335,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab /** * Return the tab trailing edge position *from the front of the fin*. */ - public double getTabTrailingEdge() { + private double getTabTrailingEdge() { switch (this.tabRelativePosition) { case FRONT: return tabLength + tabShift; @@ -388,11 +388,11 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab return getFilletMass() + getFinMass(); } - public double getFinMass() { + private double getFinMass() { return getComponentVolume() * material.getDensity(); } - public double getFilletMass() { + private double getFilletMass() { return getFilletVolume() * filletMaterial.getDensity(); } @@ -400,7 +400,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab @Override public double getComponentVolume() { // this is for the fins alone, fillets are taken care of separately. - return fins * (getFinArea() + tabHeight * tabLength) * thickness * + return finCount * (getFinArea() + tabHeight * tabLength) * thickness * crossSection.getRelativeVolume(); } @@ -414,7 +414,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab double mass = getFinMass(); double filletMass = getFilletMass(); - if (fins == 1) { + if (finCount == 1) { Transformation rotation = Transformation.rotate_x( getAngleOffset()); return rotation.transform( new Coordinate(finCGx, finCGy + getBodyRadius(), 0, (filletMass + mass))); @@ -423,7 +423,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab } } - public double getFilletVolume() { + private double getFilletVolume() { /* * Here is how the volume of the fillet is found. It assumes a circular concave * fillet tangent to the fin and the body tube. @@ -455,10 +455,10 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab } @Override - public double getOuterRadius(){ - return 0.0; + public double getBoundingRadius(){ + return 0.; } - + private void calculateAreaCG() { Coordinate[] points = this.getFinPoints(); finArea = 0; @@ -542,12 +542,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab double inertia = (h2 + 2 * w2) / 24; - if (fins == 1) + if (finCount == 1) return inertia; double radius = getBodyRadius(); - return fins * (inertia + MathUtil.pow2(MathUtil.safeSqrt(h2) + radius)); + return finCount * (inertia + MathUtil.pow2(MathUtil.safeSqrt(h2) + radius)); } @@ -577,21 +577,21 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab h = MathUtil.safeSqrt(h * area / w); } - if (fins == 1) + if (finCount == 1) return h * h / 12; double radius = getBodyRadius(); - return fins * (h * h / 12 + MathUtil.pow2(h / 2 + radius)); + return finCount * (h * h / 12 + MathUtil.pow2(h / 2 + radius)); } public BoundingBox getBoundingBox() { - BoundingBox singleFinBounds= new BoundingBox( getFinPoints()); + BoundingBox singleFinBounds= new BoundingBox().update(getFinPoints()); final double finLength = singleFinBounds.max.x; final double finHeight = singleFinBounds.max.y; - BoundingBox compBox = new BoundingBox( getComponentLocations() ); + BoundingBox compBox = new BoundingBox().update(getComponentLocations()); BoundingBox finSetBox = new BoundingBox( compBox.min.sub( 0, finHeight, finHeight ), compBox.max.add( finLength, finHeight, finHeight )); @@ -823,7 +823,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab @Override protected List copyFrom(RocketComponent c) { FinSet src = (FinSet) c; - this.fins = src.fins; + this.finCount = src.finCount; this.finRotation = src.finRotation; this.firstFinOffset = src.firstFinOffset; this.cantAngle = src.cantAngle; diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index b1a394e31..d64c0aa85 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -109,6 +109,11 @@ public class FlightConfiguration implements FlightConfigurableParameterCollection containing coordinates bounding the rocket. + * + * @deprecated Migrate to FlightConfiguration#BoundingBox, when practical. */ + @Deprecated public Collection getBounds() { + return getBoundingBox().toCollection(); + } + + /** + * Return the bounding box of the current configuration. + * + * @return + */ + public BoundingBox getBoundingBox() { if (rocket.getModID() != boundsModID) { boundsModID = rocket.getModID(); BoundingBox bounds = new BoundingBox(); for (RocketComponent component : this.getActiveComponents()) { - BoundingBox componentBounds = new BoundingBox( component.getComponentBounds() ); + BoundingBox componentBounds = new BoundingBox().update(component.getComponentBounds()); - bounds.compare( componentBounds ); + bounds.update( componentBounds ); } cachedLength = bounds.span().x; - cachedBounds.compare( bounds ); + cachedBounds.update( bounds ); } - return cachedBounds.toCollection(); + return cachedBounds; } /** diff --git a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index 8924eb3ba..32f660346 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -1,5 +1,6 @@ package net.sf.openrocket.rocketcomponent; +import java.awt.geom.Point2D; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -28,26 +29,11 @@ public class FreeformFinSet extends FinSet { this.length = 0.05; } - - + public FreeformFinSet(Coordinate[] finpoints) throws IllegalFinPointException { setPoints(finpoints); } - - /* - public FreeformFinSet(FinSet finset) { - Coordinate[] finpoints = finset.getFinPoints(); - this.copyFrom(finset); - points.clear(); - for (Coordinate c: finpoints) { - points.add(c); - } - this.length = points.get(points.size()-1).x - points.get(0).x; - } - */ - - /** * Convert an existing fin set into a freeform fin set. The specified * fin set is taken out of the rocket tree (if any) and the new component @@ -128,16 +114,12 @@ public class FreeformFinSet extends FinSet { * The point is placed at the midpoint of the current segment. * * @param index the fin point before which to add the new point. + * @param location the target location to create the new point at */ - public void addPoint(int index) { - double x0, y0, x1, y1; - - x0 = points.get(index - 1).x; - y0 = points.get(index - 1).y; - x1 = points.get(index).x; - y1 = points.get(index).y; - - points.add(index, new Coordinate((x0 + x1) / 2, (y0 + y1) / 2)); + public void addPoint(int index, Point2D.Double location) { + // new method: add new point at closest point + points.add(index, new Coordinate(location.x, location.y)); + // adding a point within the segment affects neither mass nor aerodynamics fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } @@ -215,7 +197,8 @@ public class FreeformFinSet extends FinSet { y0 = Double.NaN; x1 = points.get(1).x; y1 = points.get(1).y; - +// } else if ( (0 > index) || (points.size() <= index) ){ +// throw new IllegalFinPointException("Point Index not available!"); } else if (index == points.size() - 1) { // Restrict point diff --git a/core/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java b/core/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java index d8e7f5d57..5ee55d9b5 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java @@ -39,7 +39,7 @@ public class TrapezoidFinSet extends FinSet { public TrapezoidFinSet() { - this(3, 0.05, 0.05, 0.025, 0.03); + this(1, 0.05, 0.05, 0.025, 0.03); } // TODO: HIGH: height=0 -> CP = NaN diff --git a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java index eccba4d08..b671953c6 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java +++ b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusMethod.java @@ -34,10 +34,14 @@ public enum RadiusMethod implements DistanceMethod { @Override public double getRadius( final RocketComponent parentComponent, final RocketComponent thisComponent, final double requestedOffset ){ - if( (parentComponent instanceof BodyTube ) && (thisComponent instanceof RadiusPositionable ) ) { - return (((BodyTube)parentComponent).getOuterRadius()+((RadiusPositionable)thisComponent).getOuterRadius() + requestedOffset); + double radius = requestedOffset; + if( parentComponent instanceof BodyTube ) { + radius += ((BodyTube)parentComponent).getOuterRadius(); } - return requestedOffset; // fail-safe path + if( thisComponent instanceof RadiusPositionable ) { + radius += ((RadiusPositionable)thisComponent).getBoundingRadius(); + } + return radius; } }, @@ -47,10 +51,14 @@ public enum RadiusMethod implements DistanceMethod { SURFACE ( Application.getTranslator().get("RocketComponent.Position.Method.Radius.SURFACE") ) { @Override public double getRadius( final RocketComponent parentComponent, final RocketComponent thisComponent, final double requestedOffset ){ - if( (parentComponent instanceof BodyTube ) && (thisComponent instanceof RadiusPositionable ) ) { - return ((BodyTube)parentComponent).getOuterRadius()+((RadiusPositionable)thisComponent).getOuterRadius(); + double radius = 0.; + if( parentComponent instanceof BodyTube ) { + radius += ((BodyTube)parentComponent).getOuterRadius(); } - return 0.; // fail-safe path + if( thisComponent instanceof RadiusPositionable ) { + radius += ((RadiusPositionable)thisComponent).getBoundingRadius(); + } + return radius; } }; @@ -70,7 +78,7 @@ public enum RadiusMethod implements DistanceMethod { public String toString() { return description; } - + @Override public boolean clampToZero() { return true; } diff --git a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusPositionable.java b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusPositionable.java index d730a704d..68c749783 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/position/RadiusPositionable.java +++ b/core/src/net/sf/openrocket/rocketcomponent/position/RadiusPositionable.java @@ -1,9 +1,9 @@ package net.sf.openrocket.rocketcomponent.position; public interface RadiusPositionable { - - public double getOuterRadius(); - + + public double getBoundingRadius(); + public double getRadiusOffset(); public void setRadiusOffset(final double radius); diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index bffa62ca5..485d1dd50 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationId; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; @@ -258,19 +259,25 @@ public class BasicEventSimulationEngine implements SimulationEngine { for (MotorClusterState state : currentStatus.getActiveMotors() ){ if( state.testForIgnition(event )){ final double simulationTime = currentStatus.getSimulationTime() ; + MotorClusterState sourceState = (MotorClusterState) event.getData(); double ignitionDelay = 0; - if(( event.getType() == FlightEvent.Type.BURNOUT)|| ( event.getType() == FlightEvent.Type.EJECTION_CHARGE)){ + if (event.getType() == FlightEvent.Type.BURNOUT) + ignitionDelay = 0; + else if (event.getType() == FlightEvent.Type.EJECTION_CHARGE) ignitionDelay = sourceState.getEjectionDelay(); - } + + MotorMount mount = state.getMount(); + MotorConfiguration motorInstance = mount.getMotorConfig(this.fcid); + ignitionDelay += motorInstance.getIgnitionDelay(); + final double ignitionTime = currentStatus.getSimulationTime() + ignitionDelay; - final RocketComponent mount = (RocketComponent)state.getMount(); // TODO: this event seems to get enqueue'd multiple times ... log.info("Queueing Ignition Event for: "+state.toDescription()+" @: "+ignitionTime); //log.info(" Because of "+event.getType().name()+" @"+event.getTime()+" from: "+event.getSource().getName()); - addEvent(new FlightEvent(FlightEvent.Type.IGNITION, ignitionTime, mount, state )); + addEvent(new FlightEvent(FlightEvent.Type.IGNITION, ignitionTime, (RocketComponent) mount, state )); } } @@ -410,7 +417,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Mark the status as having dropped the booster currentStatus.getConfiguration().clearStage( stageNumber); - + // Prepare the simulation branch SimulationStatus boosterStatus = new SimulationStatus(currentStatus); boosterStatus.setFlightData(new FlightDataBranch(boosterStage.getName(), FlightDataType.TYPE_TIME)); diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index d8d48691b..9ea75832d 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -105,8 +105,6 @@ public class SimulationStatus implements Monitorable { double angle = -cond.getTheta() - (Math.PI / 2.0 - this.simulationConditions.getLaunchRodDirection()); o = Quaternion.rotation(new Coordinate(0, 0, angle)); - - // Launch rod angle and direction o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, this.simulationConditions.getLaunchRodAngle(), 0))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, Math.PI / 2.0 - this.simulationConditions.getLaunchRodDirection()))); @@ -182,6 +180,8 @@ public class SimulationStatus implements Monitorable { this.apogeeReached = orig.apogeeReached; this.tumbling = orig.tumbling; + this.configuration.copyStages(orig.configuration); + this.deployedRecoveryDevices.clear(); this.deployedRecoveryDevices.addAll(orig.deployedRecoveryDevices); diff --git a/core/src/net/sf/openrocket/util/BoundingBox.java b/core/src/net/sf/openrocket/util/BoundingBox.java index b6b30e12c..6d5f46eb8 100644 --- a/core/src/net/sf/openrocket/util/BoundingBox.java +++ b/core/src/net/sf/openrocket/util/BoundingBox.java @@ -1,5 +1,6 @@ package net.sf.openrocket.util; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; @@ -8,8 +9,7 @@ public class BoundingBox { public Coordinate max; public BoundingBox() { - min = Coordinate.MAX.setWeight( 0.0); - max = Coordinate.MIN.setWeight( 0.0); + clear(); } public BoundingBox( Coordinate _min, Coordinate _max) { @@ -18,14 +18,9 @@ public class BoundingBox { this.max = _max.clone(); } - public BoundingBox( Coordinate[] list ) { - this(); - this.compare( list); - } - - public BoundingBox( Collection list ) { - this(); - this.compare( list.toArray( new Coordinate[0] )); + public void clear() { + min = Coordinate.MAX.setWeight( 0.0); + max = Coordinate.MIN.setWeight( 0.0); } @Override @@ -33,39 +28,90 @@ public class BoundingBox { return new BoundingBox( this.min, this.max ); } - public void compare( Coordinate c ) { - compare_against_min(c); - compare_against_max(c); + + private void update_x_min( final double xVal) { + if( min.x > xVal) + min = min.setX( xVal ); } - protected void compare_against_min( Coordinate c ) { - if( min.x > c.x ) - min = min.setX( c.x ); - if( min.y > c.y ) - min = min.setY( c.y ); - if( min.z > c.z ) - min = min.setZ( c.z ); + private void update_y_min( final double yVal) { + if( min.y > yVal ) + min = min.setY( yVal ); } - protected void compare_against_max( Coordinate c) { - if( max.x < c.x ) - max = max.setX( c.x ); - if( max.y < c.y ) - max = max.setY( c.y ); - if( max.z < c.z ) - max = max.setZ( c.z ); + private void update_z_min( final double zVal) { + if( min.z > zVal ) + min = min.setZ( zVal ); + } + + private void update_x_max( final double xVal) { + if( max.x < xVal ) + max = max.setX( xVal ); + } + + private void update_y_max( final double yVal) { + if( max.y < yVal ) + max = max.setY( yVal ); + } + + private void update_z_max( final double zVal) { + if( max.z < zVal ) + max = max.setZ( zVal ); + } + + public BoundingBox update( final double val) { + update_x_min(val); + update_y_min(val); + update_z_min(val); + + update_x_max(val); + update_y_max(val); + update_z_max(val); + return this; + } + + + public void update( Coordinate c ) { + update_x_min(c.x); + update_y_min(c.y); + update_z_min(c.z); + + update_x_max(c.x); + update_y_max(c.y); + update_z_max(c.z); } - public BoundingBox compare( Coordinate[] list ) { + public BoundingBox update( Rectangle2D rect ) { + update_x_min(rect.getMinX()); + update_y_min(rect.getMinY()); + update_x_max(rect.getMaxX()); + update_y_max(rect.getMaxY()); + return this; + } + + public BoundingBox update( final Coordinate[] list ) { for( Coordinate c: list ) { - compare( c ); + update( c ); } return this; } - public void compare( BoundingBox other ) { - compare_against_min( other.min); - compare_against_max( other.max); + public BoundingBox update( Collection list ) { + for( Coordinate c: list ) { + update( c ); + } + return this; + } + + public BoundingBox update( BoundingBox other ) { + update_x_min(other.min.x); + update_y_min(other.min.y); + update_z_min(other.min.y); + + update_x_max(other.max.x); + update_y_max(other.max.y); + update_z_max(other.max.z); + return this; } public Coordinate span() { return max.sub( min ); } @@ -81,9 +127,12 @@ public class BoundingBox { return toReturn; } + public Rectangle2D toRectangle() { + return new Rectangle2D.Double(min.x, min.y, (max.x-min.x), (max.y - min.y)); + } + @Override public String toString() { -// return String.format("[( %6.4f, %6.4f, %6.4f) < ( %6.4f, %6.4f, %6.4f)]", return String.format("[( %g, %g, %g) < ( %g, %g, %g)]", min.x, min.y, min.z, max.x, max.y, max.z ); diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index e0eca4c93..9595564ad 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -942,21 +942,6 @@ public class TestRockets { FlightConfigurationId motorConfigId = selFCID; coreBody.setMotorConfig( coreMotorConfig, motorConfigId); - TrapezoidFinSet coreFins = new TrapezoidFinSet(); - coreBody.addChild(coreFins); - coreFins.setName("Core Fins"); - coreFins.setFinCount(4); - coreFins.setBaseRotation( Math.PI / 4); - coreFins.setThickness(0.003); - coreFins.setCrossSection(CrossSection.ROUNDED); - coreFins.setRootChord(0.32); - coreFins.setTipChord(0.12); - coreFins.setHeight(0.12); - coreFins.setSweep(0.18); - coreFins.setAxialMethod(AxialMethod.BOTTOM); - coreFins.setAxialOffset(0.0); - - // ====== Booster Stage Set ====== // ====== ====== ====== ====== ParallelStage boosterStage = new ParallelStage(); @@ -966,6 +951,7 @@ public class TestRockets { boosterStage.setAxialOffset(0.0); boosterStage.setInstanceCount(2); boosterStage.setRadius( RadiusMethod.SURFACE, 0.0 ); + boosterStage.setAngleMethod( AngleMethod.RELATIVE ); { NoseCone boosterCone = new NoseCone(Transition.Shape.POWER, 0.08, 0.0385); @@ -1001,6 +987,20 @@ public class TestRockets { boosterMotorConfig.setMotor( boosterMotor ); boosterMotorTubes.setMotorConfig( boosterMotorConfig, motorConfigId); boosterMotorTubes.setMotorOverhang(0.01234); + + TrapezoidFinSet boosterFins = new TrapezoidFinSet(); + boosterBody.addChild(boosterFins); + boosterFins.setName("Booster Fins"); + boosterFins.setFinCount(3); + boosterFins.setBaseRotation( Math.PI / 4); + boosterFins.setThickness(0.003); + boosterFins.setCrossSection(CrossSection.ROUNDED); + boosterFins.setRootChord(0.32); + boosterFins.setTipChord(0.12); + boosterFins.setHeight(0.12); + boosterFins.setSweep(0.18); + boosterFins.setAxialMethod(AxialMethod.BOTTOM); + boosterFins.setAxialOffset(0.0); } } diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 928204d97..068cdf54e 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -15,6 +15,7 @@ import net.sf.openrocket.ServicesForTesting; import net.sf.openrocket.plugin.PluginModule; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.ParallelStage; @@ -107,7 +108,6 @@ public class BarrowmanCalculatorTest { assertEquals(" Estes Alpha III CNa value is incorrect:", exp_cna, calcCP.weight, EPSILON); } - @Test public void testCPDoubleStrapOn() { Rocket rocket = TestRockets.makeFalcon9Heavy(); @@ -116,8 +116,8 @@ public class BarrowmanCalculatorTest { FlightConditions conditions = new FlightConditions(config); WarningSet warnings = new WarningSet(); - double expCPx = 0.994642; - double expCNa = 15.437111; + double expCPx = 1.04662388; + double expCNa = 21.5111598; Coordinate calcCP = calc.getCP(config, conditions, warnings); assertEquals(" Falcon 9 Heavy CP x value is incorrect:", expCPx, calcCP.x, EPSILON); @@ -180,7 +180,9 @@ public class BarrowmanCalculatorTest { Rocket rocket = TestRockets.makeFalcon9Heavy(); AerodynamicCalculator calc = new BarrowmanCalculator(); - ParallelStage booster = (ParallelStage)rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage)rocket.getChild(1); + final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0); + NoseCone nose = (NoseCone)booster.getChild(0); BodyTube body = (BodyTube)booster.getChild(1); diff --git a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java index 0894d9c4a..7eb12199f 100644 --- a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java @@ -63,7 +63,7 @@ public class FinSetCalcTest { double exp_cna_fins = 24.146933; double exp_cpx_fins = 0.0193484; - assertEquals(" FinSetCalc produces bad CNa: ", exp_cna_fins, forces.getCNa()*fins.getInstanceCount(), EPSILON); + assertEquals(" FinSetCalc produces bad CNa: ", exp_cna_fins, forces.getCNa(), EPSILON); assertEquals(" FinSetCalc produces bad C_p.x: ", exp_cpx_fins, forces.getCP().x, EPSILON); assertEquals(" FinSetCalc produces bad CN: ", 0.0, forces.getCN(), EPSILON); assertEquals(" FinSetCalc produces bad C_m: ", 0.0, forces.getCm(), EPSILON); @@ -97,7 +97,7 @@ public class FinSetCalcTest { double exp_cna_fins = 32.195911; double exp_cpx_fins = 0.0193484; - assertEquals(" FinSetCalc produces bad CNa: ", exp_cna_fins, forces.getCNa()*fins.getFinCount(), EPSILON); + assertEquals(" FinSetCalc produces bad CNa: ", exp_cna_fins, forces.getCNa(), EPSILON); assertEquals(" FinSetCalc produces bad C_p.x: ", exp_cpx_fins, forces.getCP().x, EPSILON); assertEquals(" FinSetCalc produces bad CN: ", 0.0, forces.getCN(), EPSILON); assertEquals(" FinSetCalc produces bad C_m: ", 0.0, forces.getCm(), EPSILON); diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index 6e24625c8..9c123c11d 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyComponent; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; @@ -190,22 +191,17 @@ public class MassCalculatorTest extends BaseTestCase { // ====== Core Stage ====== // ====== ====== ====== + final AxialStage coreStage = (AxialStage)rkt.getChild(1); { expMass = 0.1298860066700161; - cc= rkt.getChild(1).getChild(0); - compMass = cc.getComponentMass(); - assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); - - expMass = 0.21326976; - cc= rkt.getChild(1).getChild(0).getChild(0); - compMass = cc.getComponentMass(); - assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + final BodyComponent coreBody = (BodyComponent)coreStage.getChild(0); + compMass = coreBody.getComponentMass(); + assertEquals(coreBody.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); } - - + // ====== Booster Set Stage ====== // ====== ====== ====== - ParallelStage boosters = (ParallelStage) rkt.getChild(1).getChild(0).getChild(1); + ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); { expMass = 0.0222459863653; // think of the casts as an assert that ( child instanceof NoseCone) == true @@ -222,6 +218,11 @@ public class MassCalculatorTest extends BaseTestCase { InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0); compMass = mmt.getComponentMass(); assertEquals( mmt.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.15995232; + final FinSet boosterFins = (FinSet)boosters.getChild(1).getChild(1); + compMass = boosterFins.getComponentMass(); + assertEquals(boosterFins.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); } } @@ -274,21 +275,17 @@ public class MassCalculatorTest extends BaseTestCase { // ====== Core Stage ====== // ====== ====== ====== + final AxialStage coreStage = (AxialStage)rkt.getChild(1); { expCMx = 0.4; - BodyTube coreBody = (BodyTube)rkt.getChild(1).getChild(0); + BodyTube coreBody = (BodyTube)coreStage.getChild(0); actCMx = coreBody.getComponentCG().x; assertEquals("Core Body CMx calculated incorrectly: ", expCMx, actCMx, EPSILON); - - expCMx = 0.19393939; - FinSet coreFins = (FinSet)rkt.getChild(1).getChild(0).getChild(0); - actCMx = coreFins .getComponentCG().x; - assertEquals("Core Fins CMx calculated incorrectly: ", expCMx, actCMx, EPSILON); } // ====== Booster Set Stage ====== // ====== ====== ====== - ParallelStage boosters = (ParallelStage) rkt.getChild(1).getChild(0).getChild(1); + ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); { expCMx = 0.055710581052; // think of the casts as an assert that ( child instanceof NoseCone) == true @@ -305,6 +302,11 @@ public class MassCalculatorTest extends BaseTestCase { InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0); actCMx = mmt.getComponentCG().x; assertEquals(" Motor Mount Tube CMx calculated incorrectly: ", expCMx, actCMx, EPSILON); + + expCMx = 0.19393939; + FinSet boosterFins = (FinSet) boosters.getChild(1).getChild(1); + actCMx = boosterFins .getComponentCG().x; + assertEquals("Core Fins CMx calculated incorrectly: ", expCMx, actCMx, EPSILON); } } @@ -385,44 +387,37 @@ public class MassCalculatorTest extends BaseTestCase { // ====== Core Stage ====== // ====== ====== ====== + final AxialStage coreStage = (AxialStage)rocket.getChild(1); { - cc= rocket.getChild(1).getChild(0); + final BodyTube coreBody = (BodyTube)coreStage.getChild(0); expInertia = 0.000187588; - compInertia = cc.getRotationalInertia(); - assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = coreBody.getRotationalInertia(); + assertEquals(coreBody.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); expInertia = 0.00702105; - compInertia = cc.getLongitudinalInertia(); - assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = coreBody.getLongitudinalInertia(); + assertEquals(coreBody.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= rocket.getChild(1).getChild(0).getChild(0); - expInertia = 0.00734753; - compInertia = cc.getRotationalInertia(); - assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - expInertia = 0.02160236691801411; - compInertia = cc.getLongitudinalInertia(); - assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); } - - + // ====== Booster Set Stage ====== // ====== ====== ====== - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); + ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); { - cc= boosters.getChild(0); + final NoseCone boosterNose = (NoseCone)boosters.getChild(0); expInertia = 1.82665797857e-5; - compInertia = cc.getRotationalInertia(); - assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = boosterNose.getRotationalInertia(); + assertEquals(boosterNose.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); expInertia = 1.96501191666e-7; - compInertia = cc.getLongitudinalInertia(); - assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = boosterNose.getLongitudinalInertia(); + assertEquals(boosterNose.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= boosters.getChild(1); + final BodyTube boosterBody = (BodyTube)boosters.getChild(1); expInertia = 1.875878651e-4; - compInertia = cc.getRotationalInertia(); - assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = boosterBody.getRotationalInertia(); + assertEquals(boosterBody.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); expInertia = 0.00702104762; - compInertia = cc.getLongitudinalInertia(); - assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + compInertia = boosterBody.getLongitudinalInertia(); + assertEquals(boosterBody.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); cc= boosters.getChild(1).getChild(0); expInertia = 4.11444e-6; @@ -431,6 +426,15 @@ public class MassCalculatorTest extends BaseTestCase { expInertia = 3.75062e-5; compInertia = cc.getLongitudinalInertia(); assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + final FinSet boosterFins = (FinSet)boosters.getChild(1).getChild(1); + expInertia = 0.00413298; + compInertia = boosterFins.getRotationalInertia(); + assertEquals(boosterFins.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0.01215133; + compInertia = boosterFins.getLongitudinalInertia(); + assertEquals(boosterFins.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + } } @@ -469,8 +473,8 @@ public class MassCalculatorTest extends BaseTestCase { final RigidBody actualData = MassCalculator.calculateStructure( config ); final Coordinate actualCM = actualData.cm; - double expMass = 0.343156; - double expCMx = 1.134252; + double expMass = 0.12988600; + double expCMx = 0.964; assertEquals("Upper Stage Mass is incorrect: ", expMass, actualCM.weight, EPSILON); assertEquals("Upper Stage CM.x is incorrect: ", expCMx, actualCM.x, EPSILON); @@ -535,14 +539,13 @@ public class MassCalculatorTest extends BaseTestCase { FlightConfiguration config = rocket.getEmptyConfiguration(); - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); - config.setOnlyStage( boosters.getStageNumber() ); + config.setOnlyStage( TestRockets.FALCON_9H_BOOSTER_STAGE_NUMBER ); final RigidBody actualData = MassCalculator.calculateStructure( config ); final Coordinate actualCM = actualData.getCM(); - double expMass = 0.34207619524942634; - double expCMx = 0.9447396557660297; + double expMass = 0.66198084; + double expCMx = 1.08642949; assertEquals("Heavy Booster Mass is incorrect: ", expMass, actualCM.weight, EPSILON); assertEquals("Heavy Booster CM.x is incorrect: ", expCMx, actualCM.x, EPSILON); @@ -561,11 +564,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody actualBoosterLaunchData = MassCalculator.calculateLaunch( config ); double actualMass = actualBoosterLaunchData.getMass(); - double expectedMass = 1.3260761952; + double expectedMass = 1.64598084; assertEquals(" Booster Launch Mass is incorrect: ", expectedMass, actualMass, EPSILON); final Coordinate actualCM = actualBoosterLaunchData.getCM(); - double expectedCMx = 1.21899745; + double expectedCMx = 1.22267891; Coordinate expCM = new Coordinate(expectedCMx,0,0, expectedMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, actualCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, actualCM.y, EPSILON); @@ -585,8 +588,8 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody spentData = MassCalculator.calculateBurnout( config ); Coordinate spentCM = spentData.getCM(); - double expSpentMass = 0.8540761952494624; - double expSpentCMx = 1.166306978799226; + double expSpentMass = 1.17398084; + double expSpentCMx = 1.18582650; Coordinate expLaunchCM = new Coordinate( expSpentCMx, 0, 0, expSpentMass); assertEquals(" Booster Launch Mass is incorrect: ", expLaunchCM.weight, spentCM.weight, EPSILON); assertEquals(" Booster Launch CM.x is incorrect: ", expLaunchCM.x, spentCM.x, EPSILON); @@ -606,7 +609,8 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody actualPropellant = MassCalculator.calculateMotor( config ); final Coordinate actCM= actualPropellant.getCM(); - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); final MotorMount mnt = (MotorMount)boosters.getChild(1).getChild(0); final Motor boosterMotor = mnt.getMotorConfig( config.getFlightConfigurationID()).getMotor(); @@ -650,11 +654,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody spent = MassCalculator.calculateBurnout( config); - double expMOIRotational = 0.00576797953; + double expMOIRotational = 0.01593066; double boosterMOIRotational = spent.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOIRotational, boosterMOIRotational, EPSILON); - double expMOI_tr = 0.054690069584; + double expMOI_tr = 0.08018692435877221; double boosterMOI_tr= spent.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -670,9 +674,9 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody launchData = MassCalculator.calculateLaunch( config); - final double expIxx = 0.00882848653; + final double expIxx = 0.01899116; final double actIxx= launchData.getRotationalInertia(); - final double expIyy = 0.061981403261; + final double expIyy = 0.08637653; final double actIyy= launchData.getLongitudinalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expIxx, actIxx, EPSILON); @@ -689,7 +693,8 @@ public class MassCalculatorTest extends BaseTestCase { rocket.setSelectedConfiguration( config.getId() ); config.setOnlyStage( TestRockets.FALCON_9H_BOOSTER_STAGE_NUMBER ); - final ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); final double overrideMass = 0.5; boosters.setOverrideSubcomponents(true); boosters.setMassOverridden(true); @@ -712,11 +717,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM); // Validate MOI - double expMOI_axial = 0.0024481075335; + double expMOI_axial = 0.01261079; double boosterMOI_xx= burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - double expMOI_tr = 8.885103994735; + double expMOI_tr = 16.163954943504205; double boosterMOI_tr= burnout.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -729,7 +734,8 @@ public class MassCalculatorTest extends BaseTestCase { FlightConfiguration config = rocket.getEmptyConfiguration(); rocket.setSelectedConfiguration( config.getId() ); - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); config.setOnlyStage( boosters.getStageNumber() ); NoseCone nose = (NoseCone)boosters.getChild(0); @@ -747,10 +753,10 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody boosterData = MassCalculator.calculateStructure( config ); Coordinate boosterCM = boosterData.getCM(); - double expTotalMass = 3.09; + double expTotalMass = 3.40990464; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, boosterData.getMass(), EPSILON); - double expCMx = 0.81382493; + double expCMx = 0.85361377; Coordinate expCM = new Coordinate( expCMx, 0, 0, expTotalMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterCM.y, EPSILON); @@ -758,11 +764,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterCM); // Validate MOI - double expMOI_axial = 0.0213759528078421; + double expMOI_axial = 0.031538609; double boosterMOI_xx= boosterData.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - double expMOI_tr = 0.299042045787; + double expMOI_tr = 0.37548843; double boosterMOI_tr= boosterData.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -776,7 +782,8 @@ public class MassCalculatorTest extends BaseTestCase { rocket.setSelectedConfiguration( config.getId() ); config.setOnlyStage( TestRockets.FALCON_9H_BOOSTER_STAGE_NUMBER ); - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0); NoseCone nose = (NoseCone)boosters.getChild(0); nose.setCGOverridden(true); @@ -792,11 +799,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody structure = MassCalculator.calculateStructure( config); - double expMass = 0.34207619524942634; + final double expMass = 0.66198084; double calcTotalMass = structure.getMass(); assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON); - double expCMx = 1.0265399801199806; + final double expCMx = 1.12869951; Coordinate expCM = new Coordinate( expCMx, 0, 0, expMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, structure.getCM().x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, structure.getCM().y, EPSILON); @@ -804,11 +811,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, structure.getCM()); // Validate MOI - double expMOI_axial = 0.002448107533; + final double expMOI_axial = 0.012610790; double boosterMOI_xx= structure.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - double expMOI_tr = 0.031800928766; + final double expMOI_tr = 0.063491225; double boosterMOI_tr= structure.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } diff --git a/core/test/net/sf/openrocket/preset/BodyTubePresetTests.java b/core/test/net/sf/openrocket/preset/BodyTubePresetTests.java index cca185cc0..33d75293b 100644 --- a/core/test/net/sf/openrocket/preset/BodyTubePresetTests.java +++ b/core/test/net/sf/openrocket/preset/BodyTubePresetTests.java @@ -222,7 +222,7 @@ public class BodyTubePresetTests { double density = 100.0 / volume; - assertEquals("[material:TubeCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("TubeCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -264,7 +264,7 @@ public class BodyTubePresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/preset/BulkHeadPresetTests.java b/core/test/net/sf/openrocket/preset/BulkHeadPresetTests.java index 61dc2b1a4..2f393bac3 100644 --- a/core/test/net/sf/openrocket/preset/BulkHeadPresetTests.java +++ b/core/test/net/sf/openrocket/preset/BulkHeadPresetTests.java @@ -126,7 +126,7 @@ public class BulkHeadPresetTests { double density = 100.0 / volume; - assertEquals("[material:BulkHeadCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("BulkHeadCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -166,7 +166,7 @@ public class BulkHeadPresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/preset/CenteringRingPresetTests.java b/core/test/net/sf/openrocket/preset/CenteringRingPresetTests.java index 17a528669..718e4be59 100644 --- a/core/test/net/sf/openrocket/preset/CenteringRingPresetTests.java +++ b/core/test/net/sf/openrocket/preset/CenteringRingPresetTests.java @@ -222,7 +222,7 @@ public class CenteringRingPresetTests { double density = 100.0 / volume; - assertEquals("[material:CenteringRingCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("CenteringRingCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -264,7 +264,7 @@ public class CenteringRingPresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/preset/EngineBlockPresetTests.java b/core/test/net/sf/openrocket/preset/EngineBlockPresetTests.java index e8158e716..d032b5134 100644 --- a/core/test/net/sf/openrocket/preset/EngineBlockPresetTests.java +++ b/core/test/net/sf/openrocket/preset/EngineBlockPresetTests.java @@ -222,7 +222,7 @@ public class EngineBlockPresetTests { double density = 100.0 / volume; - assertEquals("[material:EngineBlockCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("EngineBlockCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -264,7 +264,7 @@ public class EngineBlockPresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/preset/LaunchLugPresetTests.java b/core/test/net/sf/openrocket/preset/LaunchLugPresetTests.java index f7f7f7b13..207931d3c 100644 --- a/core/test/net/sf/openrocket/preset/LaunchLugPresetTests.java +++ b/core/test/net/sf/openrocket/preset/LaunchLugPresetTests.java @@ -222,7 +222,7 @@ public class LaunchLugPresetTests { double density = 100.0 / volume; - assertEquals("[material:TubeCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("TubeCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -264,7 +264,7 @@ public class LaunchLugPresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/preset/NoseConePresetTests.java b/core/test/net/sf/openrocket/preset/NoseConePresetTests.java index 0cef4d55b..56e849c5b 100644 --- a/core/test/net/sf/openrocket/preset/NoseConePresetTests.java +++ b/core/test/net/sf/openrocket/preset/NoseConePresetTests.java @@ -158,7 +158,7 @@ public class NoseConePresetTests extends BaseTestCase { double density = 100.0 / volume; - assertEquals("[material:NoseConeCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("NoseConeCustom", preset.get(ComponentPreset.MATERIAL).getName()); // note - epsilon is 1% of the simple computation of density assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.01 * density); } @@ -200,7 +200,7 @@ public class NoseConePresetTests extends BaseTestCase { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); // note - epsilon is 1% of the simple computation of density assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.01 * density); } diff --git a/core/test/net/sf/openrocket/preset/TransitionPresetTests.java b/core/test/net/sf/openrocket/preset/TransitionPresetTests.java index e182a5bf2..26593dd8e 100644 --- a/core/test/net/sf/openrocket/preset/TransitionPresetTests.java +++ b/core/test/net/sf/openrocket/preset/TransitionPresetTests.java @@ -163,7 +163,7 @@ public class TransitionPresetTests extends BaseTestCase { double density = 100.0 / volume; - assertEquals("[material:TransitionCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("TransitionCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.01 * density); } @@ -214,7 +214,7 @@ public class TransitionPresetTests extends BaseTestCase { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.01 * density); } diff --git a/core/test/net/sf/openrocket/preset/TubeCouplerPresetTests.java b/core/test/net/sf/openrocket/preset/TubeCouplerPresetTests.java index f7c9a66ad..7ef16ede9 100644 --- a/core/test/net/sf/openrocket/preset/TubeCouplerPresetTests.java +++ b/core/test/net/sf/openrocket/preset/TubeCouplerPresetTests.java @@ -222,7 +222,7 @@ public class TubeCouplerPresetTests { double density = 100.0 / volume; - assertEquals("[material:TubeCustom]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("TubeCustom", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } @@ -264,7 +264,7 @@ public class TubeCouplerPresetTests { double density = 100.0 / volume; - assertEquals("[material:test]", preset.get(ComponentPreset.MATERIAL).getName()); + assertEquals("test", preset.get(ComponentPreset.MATERIAL).getName()); assertEquals(density, preset.get(ComponentPreset.MATERIAL).getDensity(), 0.0005); } diff --git a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java index f72436d05..1229b65ad 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java @@ -1,383 +1,23 @@ package net.sf.openrocket.rocketcomponent; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; -import net.sf.openrocket.aerodynamics.AerodynamicForces; -import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; -import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc; -import net.sf.openrocket.material.Material; -import net.sf.openrocket.material.Material.Type; -import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; -import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; -import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition; -import net.sf.openrocket.rocketcomponent.position.*; -import net.sf.openrocket.util.Color; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.LineStyle; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; public class FinSetTest extends BaseTestCase { - - @Test - public void testTrapezoidCGComputation() { - - { - // This is a simple square fin with sides of 1.0. - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(1); - fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - - Coordinate coords = fins.getCG(); - assertEquals(1.0, fins.getFinArea(), 0.001); - assertEquals(0.5, coords.x, 0.001); - assertEquals(0.5, coords.y, 0.001); - } - - { - // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. - // It can be decomposed into a rectangle followed by a triangle - // +---+ - // | \ - // | \ - // +------+ - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(1); - fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005); - - Coordinate coords = fins.getCG(); - assertEquals(0.75, fins.getFinArea(), 0.001); - assertEquals(0.3889, coords.x, 0.001); - assertEquals(0.4444, coords.y, 0.001); - } - - } - - @Test - public void testInstancePoints_PI_2_BaseRotation() { - // This is a simple square fin with sides of 1.0. - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(4); - fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - fins.setBaseRotation( Math.PI/2 ); - - BodyTube body = new BodyTube(1.0, 0.05 ); - body.addChild( fins ); - - Coordinate[] points = fins.getInstanceOffsets(); - - assertEquals( 0, points[0].x, 0.00001); - assertEquals( 0, points[0].y, 0.00001); - assertEquals( 0.05, points[0].z, 0.00001); - - assertEquals( 0, points[1].x, 0.00001); - assertEquals( -0.05, points[1].y, 0.00001); - assertEquals( 0, points[1].z, 0.00001); - } - - @Test - public void testInstancePoints_PI_4_BaseRotation() { - // This is a simple square fin with sides of 1.0. - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(4); - fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - fins.setBaseRotation( Math.PI/4 ); - - BodyTube body = new BodyTube(1.0, 0.05 ); - body.addChild( fins ); - - Coordinate[] points = fins.getInstanceOffsets(); - - assertEquals( 0, points[0].x, 0.0001); - assertEquals( 0.03535, points[0].y, 0.0001); - assertEquals( 0.03535, points[0].z, 0.0001); - - assertEquals( 0, points[1].x, 0.0001); - assertEquals( -0.03535, points[1].y, 0.0001); - assertEquals( 0.03535, points[1].z, 0.0001); - } - - - @Test - public void testInstanceAngles_zeroBaseRotation() { - // This is a simple square fin with sides of 1.0. - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(4); - fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - fins.setBaseRotation( 0.0 ); - double[] angles = fins.getInstanceAngles(); - - assertEquals( angles[0], 0, 0.000001 ); - assertEquals( angles[1], Math.PI/2, 0.000001 ); - assertEquals( angles[2], Math.PI, 0.000001 ); - assertEquals( angles[3], 1.5*Math.PI, 0.000001 ); - } - @Test - public void testInstanceAngles_90_BaseRotation() { - // This is a simple square fin with sides of 1.0. - TrapezoidFinSet fins = new TrapezoidFinSet(); - fins.setFinCount(4); - fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - fins.setBaseRotation( Math.PI/2 ); + public void testMultiplicity() { + final TrapezoidFinSet trapFins = new TrapezoidFinSet(); + assertEquals(1, trapFins.getFinCount()); - double[] angles = fins.getInstanceAngles(); - - assertEquals( angles[0], Math.PI/2, 0.000001 ); - assertEquals( angles[1], Math.PI, 0.000001 ); - assertEquals( angles[2], 1.5*Math.PI, 0.000001 ); - assertEquals( angles[3], 0, 0.000001 ); - } - - @Test - public void testFreeformCGComputation() throws Exception { + final FreeformFinSet fffins = new FreeformFinSet(); + assertEquals(1, fffins.getFinCount()); - { - // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. - // It can be decomposed into a rectangle followed by a triangle - // +---+ - // | \ - // | \ - // +------+ - FreeformFinSet fins = new FreeformFinSet(); - fins.setFinCount(1); - Coordinate[] points = new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0, 1), - new Coordinate(.5, 1), - new Coordinate(1, 0) - }; - fins.setPoints(points); - Coordinate coords = fins.getCG(); - assertEquals(0.75, fins.getFinArea(), 0.001); - assertEquals(0.3889, coords.x, 0.001); - assertEquals(0.4444, coords.y, 0.001); - } - - { - // This is the same trapezoid as previous free form, but it has - // some extra points along the lines. - FreeformFinSet fins = new FreeformFinSet(); - fins.setFinCount(1); - Coordinate[] points = new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0, .5), - new Coordinate(0, 1), - new Coordinate(.25, 1), - new Coordinate(.5, 1), - new Coordinate(.75, .5), - new Coordinate(1, 0) - }; - fins.setPoints(points); - Coordinate coords = fins.getCG(); - assertEquals(0.75, fins.getFinArea(), 0.001); - assertEquals(0.3889, coords.x, 0.001); - assertEquals(0.4444, coords.y, 0.001); - } - - { - // This is the same trapezoid as previous free form, but it has - // some extra points which are very close to previous points. - // in particular for points 0 & 1, - // y0 + y1 is very small. - FreeformFinSet fins = new FreeformFinSet(); - fins.setFinCount(1); - Coordinate[] points = new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0, 1E-15), - new Coordinate(0, 1), - new Coordinate(1E-15, 1), - new Coordinate(.5, 1), - new Coordinate(.5, 1 - 1E-15), - new Coordinate(1, 1E-15), - new Coordinate(1, 0) - }; - fins.setPoints(points); - Coordinate coords = fins.getCG(); - assertEquals(0.75, fins.getFinArea(), 0.001); - assertEquals(0.3889, coords.x, 0.001); - assertEquals(0.4444, coords.y, 0.001); - } - - } - - @Test - public void testWildmanVindicatorShape() throws Exception { - // This fin shape is similar to the aft fins on the Wildman Vindicator. - // A user noticed that if the y values are similar but not equal, - // the compuation of CP was incorrect because of numerical instability. - // - // +-----------------+ - // \ \ - // \ \ - // + \ - // / \ - // +---------------------+ - // - FreeformFinSet fins = new FreeformFinSet(); - fins.setFinCount(1); - Coordinate[] points = new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0.02143125, 0.01143), - new Coordinate(0.009524999999999999, 0.032543749999999996), - new Coordinate(0.041275, 0.032537399999999994), - new Coordinate(0.066675, 0) - }; - fins.setPoints(points); - Coordinate coords = fins.getCG(); - assertEquals(0.00130, fins.getFinArea(), 0.00001); - assertEquals(0.03423, coords.x, 0.00001); - assertEquals(0.01427, coords.y, 0.00001); - - BodyTube bt = new BodyTube(); - bt.addChild(fins); - FinSetCalc calc = new FinSetCalc(fins); - FlightConditions conditions = new FlightConditions(null); - AerodynamicForces forces = new AerodynamicForces(); - WarningSet warnings = new WarningSet(); - calc.calculateNonaxialForces(conditions, forces, warnings); - //System.out.println(forces); - assertEquals(0.023409, forces.getCP().x, 0.0001); - } - - @Test - public void testFreeFormCGWithNegativeY() throws Exception { - // This particular fin shape is currently not allowed in OR since the y values are negative - // however, it is possible to convert RockSim files and end up with fins which - // have negative y values. - - // A user submitted an ork file which could not be simulated because the fin - // was constructed on a tail cone. It so happened that for one pair of points - // y_n = - y_(n+1) which caused a divide by zero and resulted in CGx = NaN. - - // This Fin set is constructed to have the same problem. It is a square and rectagle - // where the two trailing edge corners of the rectangle satisfy y_0 = -y_1 - // - // +---------+ - // | | - // | | - // +----+ | - // | | - // | | - // +----+ - - FreeformFinSet fins = new FreeformFinSet(); - fins.setFinCount(1); - Coordinate[] points = new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0, 1), - new Coordinate(2, 1), - new Coordinate(2, -1), - new Coordinate(1, -1), - new Coordinate(1, 0) - }; - fins.setPoints(points); - Coordinate coords = fins.getCG(); - assertEquals(3.0, fins.getFinArea(), 0.001); - assertEquals(3.5 / 3.0, coords.x, 0.001); - assertEquals(0.5 / 3.0, coords.y, 0.001); - - } - - - @Test - public void testFreeformConvert() { - testFreeformConvert(new TrapezoidFinSet()); - testFreeformConvert(new EllipticalFinSet()); - testFreeformConvert(new FreeformFinSet()); - } - - - private void testFreeformConvert(FinSet fin) { - FreeformFinSet converted; - Material mat = Material.newMaterial(Type.BULK, "foo", 0.1, true); - - fin.setBaseRotation(1.1); - fin.setCantAngle(0.001); - fin.setCGOverridden(true); - fin.setColor(Color.BLACK); - fin.setComment("cmt"); - fin.setCrossSection(CrossSection.ROUNDED); - fin.setFinCount(5); - fin.setFinish(Finish.ROUGH); - fin.setLineStyle(LineStyle.DASHDOT); - fin.setMassOverridden(true); - fin.setMaterial(mat); - fin.setOverrideCGX(0.012); - fin.setOverrideMass(0.0123); - fin.setOverrideSubcomponents(true); - fin.setAxialOffset(0.1); - fin.setAxialMethod(AxialMethod.ABSOLUTE); - fin.setTabHeight(0.01); - fin.setTabLength(0.02); - fin.setTabRelativePosition(TabRelativePosition.END); - fin.setTabShift(0.015); - fin.setThickness(0.005); - - - converted = FreeformFinSet.convertFinSet((FinSet) fin.copy()); - - /// what do we want to ACTUALLY compare? - // ComponentCompare.assertSimilarity(fin, converted, true); // deprecated; removed - - - assertEquals(converted.getComponentName(), converted.getName()); - - - // Create test rocket - Rocket rocket = new Rocket(); - AxialStage stage = new AxialStage(); - BodyTube body = new BodyTube(); - - rocket.addChild(stage); - stage.addChild(body); - body.addChild(fin); - rocket.enableEvents(); - - Listener l1 = new Listener("l1"); - rocket.addComponentChangeListener(l1); - - fin.setName("Custom name"); - assertEquals("FinSet listener has not been notified: ", l1.changed, true); - assertEquals(ComponentChangeEvent.NONFUNCTIONAL_CHANGE, l1.changetype); - - - // Create copy - RocketComponent rocketcopy = rocket.copy(); - - Listener l2 = new Listener("l2"); - rocketcopy.addComponentChangeListener(l2); - - FinSet fincopy = (FinSet) rocketcopy.getChild(0).getChild(0).getChild(0); - FreeformFinSet.convertFinSet(fincopy); - - assertTrue("FinSet listener is changed", l2.changed); - assertEquals(ComponentChangeEvent.TREE_CHANGE, - l2.changetype & ComponentChangeEvent.TREE_CHANGE); - - } - - - private static class Listener implements ComponentChangeListener { - private boolean changed = false; - private int changetype = 0; - private final String name; - - public Listener(String name) { - this.name = name; - } - - @Override - public void componentChanged(ComponentChangeEvent e) { - assertFalse("Ensuring listener " + name + " has not been called.", changed); - changed = true; - changetype = e.getType(); - } + final EllipticalFinSet efins = new EllipticalFinSet(); + assertEquals(1, efins.getFinCount()); } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java new file mode 100644 index 000000000..803feb4f5 --- /dev/null +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -0,0 +1,299 @@ +package net.sf.openrocket.rocketcomponent; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Point2D; + +import org.junit.Test; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.material.Material.Type; +import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; +import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; +import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition; +import net.sf.openrocket.rocketcomponent.position.*; +import net.sf.openrocket.util.Color; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; + +public class FreeformFinSetTest extends BaseTestCase { + + @Test + public void testFreeformCGComputationSimpleTrapezoid() throws Exception { + // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. + // It can be decomposed into a rectangle followed by a triangle + // +---+ + // | \ + // | \ + // +------+ + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, 1), + new Coordinate(.5, 1), + new Coordinate(1, 0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + @Test + public void testFreeformCGComputationTrapezoidExtraPoints() throws Exception { + // This is the same trapezoid as previous free form, but it has + // some extra points along the lines. + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, .5), + new Coordinate(0, 1), + new Coordinate(.25, 1), + new Coordinate(.5, 1), + new Coordinate(.75, .5), + new Coordinate(1, 0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + @Test + public void testFreeformCGComputationAdjacentPoinst() throws Exception { + // This is the same trapezoid as previous free form, but it has + // some extra points which are very close to previous points. + // in particular for points 0 & 1, + // y0 + y1 is very small. + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, 1E-15), + new Coordinate(0, 1), + new Coordinate(1E-15, 1), + new Coordinate(.5, 1), + new Coordinate(.5, 1 - 1E-15), + new Coordinate(1, 1E-15), + new Coordinate(1, 0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + @Test + public void testFreeformFinAddPoint() throws Exception { + FreeformFinSet fin = new FreeformFinSet(); + fin.setFinCount(1); + fin.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0.5, 1.0), + new Coordinate(1.0, 1.0), + new Coordinate(1, 0) + }; + fin.setPoints(points); + assertEquals(4, fin.getPointCount()); + + // +--+ + // / |x + // / | + // +=====+ + Point2D.Double toAdd = new Point2D.Double(1.01, 0.8); + fin.addPoint(3, toAdd); + + assertEquals(5, fin.getPointCount()); + final Coordinate added = fin.getFinPoints()[3]; + assertEquals(1.1,added.x, 0.1); + assertEquals(0.8, added.y, 0.1); + } + + @Test + public void testWildmanVindicatorShape() throws Exception { + // This fin shape is similar to the aft fins on the Wildman Vindicator. + // A user noticed that if the y values are similar but not equal, + // the compuation of CP was incorrect because of numerical instability. + // + // +-----------------+ + // \ \ + // \ \ + // + \ + // / \ + // +---------------------+ + // + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0.02143125, 0.01143), + new Coordinate(0.009524999999999999, 0.032543749999999996), + new Coordinate(0.041275, 0.032537399999999994), + new Coordinate(0.066675, 0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.00130, fins.getFinArea(), 0.00001); + assertEquals(0.03423, coords.x, 0.00001); + assertEquals(0.01427, coords.y, 0.00001); + + BodyTube bt = new BodyTube(); + bt.addChild(fins); + FinSetCalc calc = new FinSetCalc(fins); + FlightConditions conditions = new FlightConditions(null); + AerodynamicForces forces = new AerodynamicForces(); + WarningSet warnings = new WarningSet(); + calc.calculateNonaxialForces(conditions, forces, warnings); + //System.out.println(forces); + assertEquals(0.023409, forces.getCP().x, 0.0001); + } + + @Test + public void testFreeFormCGWithNegativeY() throws Exception { + // This particular fin shape is currently not allowed in OR since the y values are negative + // however, it is possible to convert RockSim files and end up with fins which + // have negative y values. + + // A user submitted an ork file which could not be simulated because the fin + // was constructed on a tail cone. It so happened that for one pair of points + // y_n = - y_(n+1) which caused a divide by zero and resulted in CGx = NaN. + + // This Fin set is constructed to have the same problem. It is a square and rectagle + // where the two trailing edge corners of the rectangle satisfy y_0 = -y_1 + // + // +---------+ + // | | + // | | + // +----+ | + // | | + // | | + // +----+ + + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, 1), + new Coordinate(2, 1), + new Coordinate(2, -1), + new Coordinate(1, -1), + new Coordinate(1, 0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(3.0, fins.getFinArea(), 0.001); + assertEquals(3.5 / 3.0, coords.x, 0.001); + assertEquals(0.5 / 3.0, coords.y, 0.001); + + } + + + @Test + public void testFreeformConvert() { + testFreeformConvert(new TrapezoidFinSet()); + testFreeformConvert(new EllipticalFinSet()); + testFreeformConvert(new FreeformFinSet()); + } + + + private void testFreeformConvert(FinSet fin) { + FreeformFinSet converted; + Material mat = Material.newMaterial(Type.BULK, "foo", 0.1, true); + + fin.setBaseRotation(1.1); + fin.setCantAngle(0.001); + fin.setCGOverridden(true); + fin.setColor(Color.BLACK); + fin.setComment("cmt"); + fin.setCrossSection(CrossSection.ROUNDED); + fin.setFinCount(5); + fin.setFinish(Finish.ROUGH); + fin.setLineStyle(LineStyle.DASHDOT); + fin.setMassOverridden(true); + fin.setMaterial(mat); + fin.setOverrideCGX(0.012); + fin.setOverrideMass(0.0123); + fin.setOverrideSubcomponents(true); + fin.setAxialOffset(0.1); + fin.setAxialMethod(AxialMethod.ABSOLUTE); + fin.setTabHeight(0.01); + fin.setTabLength(0.02); + fin.setTabRelativePosition(TabRelativePosition.END); + fin.setTabShift(0.015); + fin.setThickness(0.005); + + + converted = FreeformFinSet.convertFinSet((FinSet) fin.copy()); + + /// what do we want to ACTUALLY compare? + // ComponentCompare.assertSimilarity(fin, converted, true); // deprecated; removed + + + assertEquals(converted.getComponentName(), converted.getName()); + + + // Create test rocket + Rocket rocket = new Rocket(); + AxialStage stage = new AxialStage(); + BodyTube body = new BodyTube(); + + rocket.addChild(stage); + stage.addChild(body); + body.addChild(fin); + rocket.enableEvents(); + + Listener l1 = new Listener("l1"); + rocket.addComponentChangeListener(l1); + + fin.setName("Custom name"); + assertEquals("FinSet listener has not been notified: ", l1.changed, true); + assertEquals(ComponentChangeEvent.NONFUNCTIONAL_CHANGE, l1.changetype); + + + // Create copy + RocketComponent rocketcopy = rocket.copy(); + + Listener l2 = new Listener("l2"); + rocketcopy.addComponentChangeListener(l2); + + FinSet fincopy = (FinSet) rocketcopy.getChild(0).getChild(0).getChild(0); + FreeformFinSet.convertFinSet(fincopy); + + assertTrue("FinSet listener is changed", l2.changed); + assertEquals(ComponentChangeEvent.TREE_CHANGE, + l2.changetype & ComponentChangeEvent.TREE_CHANGE); + + } + + + private static class Listener implements ComponentChangeListener { + private boolean changed = false; + private int changetype = 0; + private final String name; + + public Listener(String name) { + this.name = name; + } + + @Override + public void componentChanged(ComponentChangeEvent e) { + assertFalse("Ensuring listener " + name + " has not been called.", changed); + changed = true; + changetype = e.getType(); + } + } + +} diff --git a/core/test/net/sf/openrocket/rocketcomponent/ParallelStageTest.java b/core/test/net/sf/openrocket/rocketcomponent/ParallelStageTest.java index ccd83a88a..74dd3d49b 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/ParallelStageTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/ParallelStageTest.java @@ -133,14 +133,6 @@ public class ParallelStageTest extends BaseTestCase { RocketComponent coreBody = coreStage.getChild(0); Assert.assertEquals( coreBody.getPosition().x, 0.0, EPSILON); Assert.assertEquals( coreBody.getComponentLocations()[0].x, expectedCoreStageX, EPSILON); - - FinSet coreFins = (FinSet)coreBody.getChild(0); - - // default is offset=0, method=BOTTOM - assertEquals( AxialMethod.BOTTOM, coreFins.getAxialMethod() ); - assertEquals( 0.0, coreFins.getAxialOffset(), EPSILON); - assertEquals( 0.480, coreFins.getPosition().x, EPSILON); - assertEquals( 1.044, coreFins.getComponentLocations()[0].x, EPSILON); } @@ -151,7 +143,7 @@ public class ParallelStageTest extends BaseTestCase { AxialStage sustainer = (AxialStage) rocket.getChild(0); AxialStage coreStage = (AxialStage) rocket.getChild(1); - AxialStage booster = (AxialStage) coreStage.getChild(0).getChild(1); + AxialStage booster = (AxialStage) coreStage.getChild(0).getChild(0); AxialStage sustainerPrev = sustainer.getUpperStage(); assertThat("sustainer parent is not found correctly: ", sustainerPrev, equalTo(null)); @@ -194,7 +186,7 @@ public class ParallelStageTest extends BaseTestCase { public void testBoosterInitializationFREERadius() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage parallelBoosterSet = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage parallelBoosterSet = (ParallelStage)coreStage.getChild(0).getChild(0); // vvvv function under test parallelBoosterSet.setRadiusMethod( RadiusMethod.FREE ); @@ -214,7 +206,7 @@ public class ParallelStageTest extends BaseTestCase { public void testBoosterInitializationSURFACERadius() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage parallelBoosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage parallelBoosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); final BodyTube coreBody = (BodyTube)coreStage.getChild(0); final BodyTube boosterBody = (BodyTube)parallelBoosterStage.getChild(1); @@ -257,7 +249,7 @@ public class ParallelStageTest extends BaseTestCase { public void testBoosterInitializationRELATIVERadius() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage parallelBoosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage parallelBoosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); final BodyTube coreBody = (BodyTube)coreStage.getChild(0); final BodyTube boosterBody = (BodyTube)parallelBoosterStage.getChild(1); @@ -299,7 +291,7 @@ public class ParallelStageTest extends BaseTestCase { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); final BodyTube coreBody = (BodyTube)coreStage.getChild(0); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); final BodyTube boosterBody = (BodyTube)boosterStage.getChild(1); // vv function under test @@ -335,7 +327,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetStagePosition_outsideABSOLUTE() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final BodyTube coreBody= (BodyTube) rocket.getChild(1).getChild(0); - final ParallelStage boosterStage = (ParallelStage)coreBody.getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreBody.getChild(0); double targetAbsoluteX = 0.8; double expectedRelativeX = 0.236; @@ -386,7 +378,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetStagePosition_outsideTOP() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); double targetOffset = 0.2; @@ -417,7 +409,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetMIDDLE() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); // when 'external' the stage should be freely movable // vv function under test @@ -436,7 +428,8 @@ public class ParallelStageTest extends BaseTestCase { @Test public void testSetBOTTOM() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); - final ParallelStage boosterStage = (ParallelStage)rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); // vv function under test double targetOffset = 0.2; @@ -454,7 +447,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetTOP_getABSOLUTE() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); double targetOffset = 0.2; @@ -480,7 +473,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetTOP_getAFTER() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); double targetOffset = 0.2; @@ -503,7 +496,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetTOP_getMIDDLE() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); double targetOffset = 0.2; @@ -525,7 +518,7 @@ public class ParallelStageTest extends BaseTestCase { public void testSetTOP_getBOTTOM() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); double targetOffset = 0.2; @@ -547,7 +540,8 @@ public class ParallelStageTest extends BaseTestCase { @Test public void testSetBOTTOM_getTOP() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); - final ParallelStage boosterStage = (ParallelStage)rocket.getChild(1).getChild(0).getChild(1); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); // vv function under test double targetOffset = 0.2; @@ -568,7 +562,7 @@ public class ParallelStageTest extends BaseTestCase { public void testOutsideStageRepositionTOPAfterAdd() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); final AxialStage coreStage = (AxialStage) rocket.getChild(1); - final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(1); + final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0); final double targetOffset = +2.50; final AxialMethod targetMethod = AxialMethod.TOP; @@ -597,7 +591,9 @@ public class ParallelStageTest extends BaseTestCase { @Test public void testStageInitializationMethodValueOrder() { final RocketComponent rocket = TestRockets.makeFalcon9Heavy(); - final BodyTube coreBody = (BodyTube) rocket.getChild(1).getChild(0); + final AxialStage coreStage = (AxialStage) rocket.getChild(1); + final BodyTube coreBody = (BodyTube)coreStage.getChild(0); + ParallelStage boosterA = createExtraBooster(); boosterA.setName("Booster A Stage"); @@ -632,7 +628,7 @@ public class ParallelStageTest extends BaseTestCase { final AxialStage coreStage = (AxialStage) rocket.getChild(1); final BodyTube coreBody = (BodyTube) coreStage.getChild(0); - ParallelStage boosterA = (ParallelStage)coreBody.getChild(1); + ParallelStage boosterA = (ParallelStage)coreBody.getChild(0); ParallelStage boosterB = createExtraBooster(); boosterB.setName("Booster A Stage"); @@ -665,8 +661,8 @@ public class ParallelStageTest extends BaseTestCase { actualStageNumber = boosterC.getStageNumber(); assertEquals(" init order error: Booster B: resultant positions: ", expectedStageNumber, actualStageNumber); - // remove Booster A - coreBody.removeChild(2); + // remove Booster B + coreBody.removeChild(1); String treedump = rocket.toDebugTree(); int expectedStageCount = 4; diff --git a/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java b/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java index 17d2d0570..c37536cfe 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java @@ -242,7 +242,7 @@ public class RocketTest extends BaseTestCase { // ====== Booster Set Stage ====== // ====== ====== ====== - ParallelStage boosters = (ParallelStage) coreBody.getChild(1); + ParallelStage boosters = (ParallelStage) coreBody.getChild(0); { assertEquals( RadiusMethod.SURFACE, boosters.getRadiusMethod() ); assertEquals( AngleMethod.RELATIVE, boosters.getAngleMethod() ); @@ -277,19 +277,21 @@ public class RocketTest extends BaseTestCase { loc = boosterBody.getComponentLocations()[0]; assertEquals(boosterBody.getName()+" offset is incorrect: ", 0.08, offset.x, EPSILON); assertEquals(boosterBody.getName()+" location is incorrect: ", 0.564, loc.x, EPSILON); + { + InnerTube mmt = (InnerTube)boosterBody.getChild(0); + offset = mmt.getPosition(); + loc = mmt.getComponentLocations()[0]; + assertEquals(mmt.getName()+" offset is incorrect: ", 0.65, offset.x, EPSILON); + assertEquals(mmt.getName()+" location is incorrect: ", 1.214, loc.x, EPSILON); - InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0); - offset = mmt.getPosition(); - loc = mmt.getComponentLocations()[0]; - assertEquals(mmt.getName()+" offset is incorrect: ", 0.65, offset.x, EPSILON); - assertEquals(mmt.getName()+" location is incorrect: ", 1.214, loc.x, EPSILON); + final FinSet coreFins = (FinSet)boosterBody.getChild(1); + offset = coreFins.getPosition(); + loc = coreFins.getComponentLocations()[0]; + assertEquals(coreFins.getName()+" offset is incorrect: ", 0.480, offset.x, EPSILON); + assertEquals(coreFins.getName()+" location is incorrect: ", 1.044, loc.x, EPSILON); + } } - FinSet coreFins = (FinSet)rocket.getChild(1).getChild(0).getChild(0); - offset = coreFins.getPosition(); - loc = coreFins.getComponentLocations()[0]; - assertEquals(coreFins.getName()+" offset is incorrect: ", 0.480, offset.x, EPSILON); - assertEquals(coreFins.getName()+" location is incorrect: ", 1.044, loc.x, EPSILON); } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java new file mode 100644 index 000000000..0267c64ae --- /dev/null +++ b/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java @@ -0,0 +1,123 @@ +package net.sf.openrocket.rocketcomponent; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; + +public class TrapezoidFinSetTest extends BaseTestCase { + + @Test + public void testTrapezoidCGComputation() { + + { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(1); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + + Coordinate coords = fins.getCG(); + assertEquals(1.0, fins.getFinArea(), 0.001); + assertEquals(0.5, coords.x, 0.001); + assertEquals(0.5, coords.y, 0.001); + } + + { + // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. + // It can be decomposed into a rectangle followed by a triangle + // +---+ + // | \ + // | \ + // +------+ + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(1); + fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005); + + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + } + + @Test + public void testInstancePoints_PI_2_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/2 ); + + BodyTube body = new BodyTube(1.0, 0.05 ); + body.addChild( fins ); + + Coordinate[] points = fins.getInstanceOffsets(); + + assertEquals( 0, points[0].x, 0.00001); + assertEquals( 0, points[0].y, 0.00001); + assertEquals( 0.05, points[0].z, 0.00001); + + assertEquals( 0, points[1].x, 0.00001); + assertEquals( -0.05, points[1].y, 0.00001); + assertEquals( 0, points[1].z, 0.00001); + } + + @Test + public void testInstancePoints_PI_4_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/4 ); + + BodyTube body = new BodyTube(1.0, 0.05 ); + body.addChild( fins ); + + Coordinate[] points = fins.getInstanceOffsets(); + + assertEquals( 0, points[0].x, 0.0001); + assertEquals( 0.03535, points[0].y, 0.0001); + assertEquals( 0.03535, points[0].z, 0.0001); + + assertEquals( 0, points[1].x, 0.0001); + assertEquals( -0.03535, points[1].y, 0.0001); + assertEquals( 0.03535, points[1].z, 0.0001); + } + + + @Test + public void testInstanceAngles_zeroBaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( 0.0 ); + + double[] angles = fins.getInstanceAngles(); + + assertEquals( angles[0], 0, 0.000001 ); + assertEquals( angles[1], Math.PI/2, 0.000001 ); + assertEquals( angles[2], Math.PI, 0.000001 ); + assertEquals( angles[3], 1.5*Math.PI, 0.000001 ); + } + + @Test + public void testInstanceAngles_90_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/2 ); + + double[] angles = fins.getInstanceAngles(); + + assertEquals( angles[0], Math.PI/2, 0.000001 ); + assertEquals( angles[1], Math.PI, 0.000001 ); + assertEquals( angles[2], 1.5*Math.PI, 0.000001 ); + assertEquals( angles[3], 0, 0.000001 ); + } + +} diff --git a/lib-test/OpenRocket Test Libraries.iml b/lib-test/OpenRocket Test Libraries.iml index 82b2558c0..b9af6028c 100644 --- a/lib-test/OpenRocket Test Libraries.iml +++ b/lib-test/OpenRocket Test Libraries.iml @@ -10,11 +10,12 @@ - + + @@ -78,6 +79,5 @@ - \ No newline at end of file diff --git a/swing/OpenRocket Swing.iml b/swing/OpenRocket Swing.iml index 27d5f85ad..95b144e5f 100644 --- a/swing/OpenRocket Swing.iml +++ b/swing/OpenRocket Swing.iml @@ -24,7 +24,7 @@ - + @@ -207,6 +207,76 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java index 96b01d893..42e761dfb 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java @@ -135,7 +135,7 @@ public abstract class FinSetConfig extends RocketComponentConfig { } - public JPanel finTabPanel() { + private JPanel finTabPanel() { JPanel panel = new JPanel( new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%", "[150lp::][65lp::][30lp::][200lp::]", "")); @@ -185,14 +185,14 @@ public abstract class FinSetConfig extends RocketComponentConfig { label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight")); panel.add(label, "gapleft para"); - final DoubleModel mth = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0); - - spin = new JSpinner(mth.getSpinnerModel()); + final DoubleModel tabHeightModel = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0); + component.addChangeListener( tabHeightModel ); + spin = new JSpinner(tabHeightModel.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); panel.add(spin, "growx"); - panel.add(new UnitSelector(mth), "growx"); - panel.add(new BasicSlider(mth.getSliderModel(DoubleModel.ZERO, length2)), + panel.add(new UnitSelector(tabHeightModel), "growx"); + panel.add(new BasicSlider(tabHeightModel.getSliderModel(DoubleModel.ZERO, length2)), "w 100lp, growx 5, wrap"); //// Tab position: @@ -202,7 +202,7 @@ public abstract class FinSetConfig extends RocketComponentConfig { panel.add(label, "gapleft para"); final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH); - + component.addChangeListener( mts); spin = new JSpinner(mts.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); panel.add(spin, "growx"); @@ -214,9 +214,10 @@ public abstract class FinSetConfig extends RocketComponentConfig { label = new JLabel(trans.get("FinSetConfig.lbl.relativeto")); panel.add(label, "right, gapright unrel"); - final EnumModel em = new EnumModel(component, "TabRelativePosition"); + + final EnumModel em = new EnumModel<>(component, "TabRelativePosition"); - JComboBox enumCombo = new JComboBox(em); + JComboBox enumCombo = new JComboBox<>(em); panel.add( enumCombo, "spanx 3, growx, wrap para"); @@ -233,7 +234,7 @@ public abstract class FinSetConfig extends RocketComponentConfig { try { document.startUndo("Compute fin tabs"); - List rings = new ArrayList(); + List rings = new ArrayList<>(); //Do deep recursive iteration Iterator iter = parent.iterator(false); while (iter.hasNext()) { @@ -244,8 +245,8 @@ public abstract class FinSetConfig extends RocketComponentConfig { double depth = ((Coaxial) parent).getOuterRadius() - it.getOuterRadius(); //Set fin tab depth if (depth >= 0.0d) { - mth.setValue(depth); - mth.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit()); + tabHeightModel.setValue(depth); + tabHeightModel.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit()); } } } else if (rocketComponent instanceof CenteringRing) { @@ -254,8 +255,8 @@ public abstract class FinSetConfig extends RocketComponentConfig { } //Figure out position and length of the fin tab if (!rings.isEmpty()) { - FinSet.TabRelativePosition temp = (FinSet.TabRelativePosition) em.getSelectedItem(); - em.setSelectedItem(FinSet.TabRelativePosition.FRONT); + AxialMethod temp = (AxialMethod) em.getSelectedItem(); + em.setSelectedItem(AxialMethod.TOP); double len = computeFinTabLength(rings, component.asPositionValue(AxialMethod.TOP), component.getLength(), mts, parent); mtl.setValue(len); @@ -506,10 +507,11 @@ public abstract class FinSetConfig extends RocketComponentConfig { label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects")); filletPanel.add(label, "spanx 4, wrap rel"); - JComboBox combo = new JComboBox<>(new MaterialModel(filletPanel, component, Material.Type.BULK, "FilletMaterial")); + JComboBox materialCombo = new JComboBox(new MaterialModel(filletPanel, component, Material.Type.BULK, "FilletMaterial")); + //// The component material affects the weight of the component. - combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects")); - filletPanel.add(combo, "spanx 4, growx, wrap paragraph"); + materialCombo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects")); + filletPanel.add( materialCombo, "spanx 4, growx, wrap paragraph"); filletPanel.setToolTipText(tip); return filletPanel; } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 92dfeafa5..64fe1f06f 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -1,18 +1,17 @@ package net.sf.openrocket.gui.configdialog; - import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.io.BufferedWriter; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; + import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.List; @@ -63,8 +62,9 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Coordinate; +@SuppressWarnings("serial") public class FreeformFinSetConfig extends FinSetConfig { - private static final long serialVersionUID = 2504130276828826021L; + private static final Logger log = LoggerFactory.getLogger(FreeformFinSetConfig.class); private static final Translator trans = Application.getTranslator(); @@ -72,6 +72,8 @@ public class FreeformFinSetConfig extends FinSetConfig { private JTable table = null; private FinPointTableModel tableModel = null; + private int dragIndex = -1; + private FinPointFigure figure = null; @@ -146,7 +148,7 @@ public class FreeformFinSetConfig extends FinSetConfig { //// Position relative to: panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.Posrelativeto"))); - JComboBox positionCombo = new JComboBox( new EnumModel(component, "AxialMethod", AxialMethod.axialOffsetMethods )); + JComboBox positionCombo = new JComboBox<>( new EnumModel<>(component, "AxialMethod", AxialMethod.axialOffsetMethods )); panel.add(positionCombo, "spanx 3, growx, wrap"); //// plus panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.plus")), "right"); @@ -159,10 +161,7 @@ public class FreeformFinSetConfig extends FinSetConfig { panel.add(new UnitSelector(m), "growx"); panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), new DoubleModel(component.getParent(), "Length"))), "w 100lp, wrap"); - - - - + mainPanel.add(panel, "aligny 20%"); mainPanel.add(new JSeparator(SwingConstants.VERTICAL), "growy, height 150lp"); @@ -170,12 +169,10 @@ public class FreeformFinSetConfig extends FinSetConfig { panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); - - //// Cross section //// Fin cross section: panel.add(new JLabel(trans.get("FreeformFinSetCfg.lbl.FincrossSection")), "span, split"); - JComboBox sectionCombo = new JComboBox(new EnumModel(component, "CrossSection")); + JComboBox sectionCombo = new JComboBox<>(new EnumModel(component, "CrossSection")); panel.add(sectionCombo, "growx, wrap unrel"); @@ -204,16 +201,14 @@ public class FreeformFinSetConfig extends FinSetConfig { } - + // edit fin points directly here private JPanel shapePane() { - JPanel panel = new JPanel(new MigLayout("fill")); + JPanel panel = new JPanel(null); // Create the figure figure = new FinPointFigure(finset); - ScaleScrollPane figurePane = new FinPointScrollPane(); - figurePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - figurePane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + ScaleScrollPane figurePane = new FinPointScrollPane( figure); // Create the table tableModel = new FinPointTableModel(); @@ -222,6 +217,14 @@ public class FreeformFinSetConfig extends FinSetConfig { for (int i = 0; i < Columns.values().length; i++) { table.getColumnModel().getColumn(i).setPreferredWidth(Columns.values()[i].getWidth()); } + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent ev) { + figure.setSelectedIndex(table.getSelectedRow()); + figure.updateFigure(); + } + + }); JScrollPane tablePane = new JScrollPane(table); JButton scaleButton = new JButton(trans.get("FreeformFinSetConfig.lbl.scaleFin")); @@ -244,106 +247,81 @@ public class FreeformFinSetConfig extends FinSetConfig { public void actionPerformed(ActionEvent e) { log.info(Markers.USER_MARKER, "Export CSV free-form fin"); - JFileChooser c = new JFileChooser(); - // Demonstrate "Save" dialog: - int rVal = c.showSaveDialog(FreeformFinSetConfig.this); - if (rVal == JFileChooser.APPROVE_OPTION) { - File myFile = c.getSelectedFile(); + JFileChooser chooser = new JFileChooser(); + // Demonstrate "Save" dialog: - Writer writer = null; - int nRow = table.getRowCount(); - int nCol = table.getColumnCount(); - try{ - try { - writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(myFile.getAbsoluteFile()), "utf-8")); + if (JFileChooser.APPROVE_OPTION == chooser.showSaveDialog(FreeformFinSetConfig.this)){ + File selectedFile= chooser.getSelectedFile(); - //write the header information - StringBuffer bufferHeader = new StringBuffer(); - for (int j = 0; j < nCol; j++) { - bufferHeader.append(table.getColumnName(j)); - if (j!=nCol) bufferHeader.append(", "); - } - writer.write(bufferHeader.toString() + "\r\n"); - - //write row information - for (int i = 0 ; i < nRow ; i++){ - StringBuffer buffer = new StringBuffer(); - for (int j = 0 ; j < nCol ; j++){ - buffer.append(table.getValueAt(i,j)); - if (j!=nCol) buffer.append(", "); - } - writer.write(buffer.toString() + "\r\n"); - } - }finally { - writer.close(); - } - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (FileNotFoundException e1) { - e1.printStackTrace(); - } catch (IOException e1) { - e1.printStackTrace(); - } - - } - } - }); - - panel.add(tablePane, "growy, width 100lp:100lp:, height 100lp:250lp:"); - panel.add(figurePane, "gap unrel, spanx, spany 3, growx, growy 1000, height 100lp:250lp:, wrap"); - - panel.add(new StyledLabel(trans.get("lbl.doubleClick1"), -2), "alignx 50%, wrap"); - panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "alignx 50%, wrap"); - - panel.add(scaleButton, "spany 2, alignx 50%, aligny 50%"); - panel.add(exportCsvButton, "spany 2, alignx 50%, aligny 50%"); - panel.add(new ScaleSelector(figurePane), "spany 2, aligny 50%"); - - JButton importButton = new JButton(trans.get("CustomFinImport.button.label")); - importButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - importImage(); + FreeformFinSetConfig.writeCSVFile(table, selectedFile); + } } }); - panel.add(importButton, "spany 2, bottom"); + JButton importButton = new JButton(trans.get("CustomFinImport.button.label")); + importButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + importImage(); + } + }); + ScaleSelector selector = new ScaleSelector(figurePane); + // fit on first start-up + figurePane.setFitting(true); + + panel.setLayout(new MigLayout("fill, gap 5!","", "[nogrid, fill, sizegroup display, growprio 200]5![sizegroup text, growprio 5]5![sizegroup buttons, align top, growprio 5]0!")); + + // first row: main display + panel.add(tablePane, "width 100lp:100lp:, growy"); + panel.add(figurePane, "width 200lp:400lp:, gap unrel, grow, height 100lp:250lp:, wrap"); + + // 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"); + + // row of controls at the bottom of the tab: + panel.add(selector, "aligny bottom, gap unrel"); + panel.add(scaleButton, ""); + panel.add(importButton, ""); + panel.add(exportCsvButton, ""); // panel.add(new CustomFinBmpImporter(finset), "spany 2, bottom"); - panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "right, wrap"); - panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.ctrlClick"), -2), "right"); return panel; } - - public void writeCSVfile(JTable table, String filename) throws IOException{ - Writer writer = null; - int nRow = table.getRowCount(); - int nCol = table.getColumnCount(); - try { - writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), "utf-8")); - //write the header information - StringBuffer bufferHeader = new StringBuffer(); - for (int j = 0; j < nCol; j++) { - bufferHeader.append(table.getColumnName(j)); - if (j!=nCol) bufferHeader.append(", "); - } - writer.write(bufferHeader.toString() + "\r\n"); + private static void writeCSVFile(JTable table, final File outputFile){ + int nRow = table.getRowCount(); + int nCol = table.getColumnCount(); + + try { + final Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "utf-8")); + + //write the header information + StringBuilder bufferHeader = new StringBuilder(); + for (int j = 0; j < nCol; j++) { + bufferHeader.append(table.getColumnName(j)); + bufferHeader.append(", "); + } + writer.write(bufferHeader.toString() + "\r\n"); + + //write row information + for (int i = 0; i < nRow; i++) { + StringBuilder buffer = new StringBuilder(); + for (int j = 0; j < nCol; j++) { + buffer.append(table.getValueAt(i, j)); + buffer.append(", "); + } + writer.write(buffer.toString() + "\r\n"); + } + writer.close(); + + } catch (IOException e1) { + e1.printStackTrace(); + } + } + - //write row information - for (int i = 0 ; i < nRow ; i++){ - StringBuffer buffer = new StringBuffer(); - for (int j = 0 ; j < nCol ; j++){ - buffer.append(table.getValueAt(i,j)); - if (j!=nCol) buffer.append(", "); - } - writer.write(buffer.toString() + "\r\n"); - } - } finally { - writer.close(); - } - } - private void importImage() { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter(FileHelper.getImageFileFilter()); @@ -361,7 +339,7 @@ public class FreeformFinSetConfig extends FinSetConfig { CustomFinImporter importer = new CustomFinImporter(); List points = importer.getPoints(chooser.getSelectedFile()); document.startUndo(trans.get("CustomFinImport.undo")); - finset.setPoints(points); + finset.setPoints( points); } catch (IllegalFinPointException e) { log.warn("Error storing fin points", e); JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"), @@ -373,8 +351,7 @@ public class FreeformFinSetConfig extends FinSetConfig { } finally { document.stopUndo(); } - } - + } } @@ -384,75 +361,80 @@ public class FreeformFinSetConfig extends FinSetConfig { if (tableModel != null) { tableModel.fireTableDataChanged(); + + // make sure to do this *after* the table data is updated. + if( 0 <= this.dragIndex ) { + table.setRowSelectionInterval(dragIndex, dragIndex); + }else { + table.clearSelection(); + } } + if (figure != null) { - figure.updateFigure(); + if( 0 <= this.dragIndex ) { + figure.setSelectedIndex(dragIndex); + }else{ + figure.resetSelectedIndex(); + } + figure.updateFigure(); } + + revalidate(); + repaint(); } - - - private class FinPointScrollPane extends ScaleScrollPane { - private static final long serialVersionUID = 2232218393756983666L; private static final int ANY_MASK = (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK | MouseEvent.META_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK); - private int dragIndex = -1; - public FinPointScrollPane() { - super(figure, false); // Disallow fitting as it's buggy + + private FinPointScrollPane( final FinPointFigure _figure) { + super( _figure); } @Override public void mousePressed(MouseEvent event) { int mods = event.getModifiersEx(); - if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != 0) { - super.mousePressed(event); + final int pressIndex = getPoint(event); + if ( pressIndex >= 0) { + dragIndex = pressIndex; + updateFields(); return; } - int index = getPoint(event); - if (index >= 0) { - dragIndex = index; - return; - } - index = getSegment(event); - if (index >= 0) { + final int segmentIndex = getSegment(event); + if (segmentIndex >= 0) { Point2D.Double point = getCoordinates(event); - finset.addPoint(index); - try { - finset.setPoint(index, point.x, point.y); - } catch (IllegalFinPointException ignore) { - } - dragIndex = index; - + finset.addPoint(segmentIndex, point); + + dragIndex = segmentIndex; + updateFields(); return; } super.mousePressed(event); - return; } - @Override public void mouseDragged(MouseEvent event) { - int mods = event.getModifiersEx(); - if (dragIndex < 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) { + int mods = event.getModifiersEx(); + if (dragIndex <= 0 || (mods & (ANY_MASK | MouseEvent.BUTTON1_DOWN_MASK)) != MouseEvent.BUTTON1_DOWN_MASK) { super.mouseDragged(event); return; } - Point2D.Double point = getCoordinates(event); + Point2D.Double point = getCoordinates(event); try { - finset.setPoint(dragIndex, point.x, point.y); + finset.setPoint(dragIndex, point.x, point.y); } catch (IllegalFinPointException ignore) { - log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y); - } + log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y); + } + + updateFields(); } - @Override public void mouseReleased(MouseEvent event) { dragIndex = -1; @@ -461,24 +443,22 @@ public class FreeformFinSetConfig extends FinSetConfig { @Override public void mouseClicked(MouseEvent event) { - int mods = event.getModifiersEx(); - if (event.getButton() != MouseEvent.BUTTON1 || (mods & ANY_MASK) != MouseEvent.CTRL_DOWN_MASK) { - super.mouseClicked(event); - return; - } - - int index = getPoint(event); - if (index < 0) { - super.mouseClicked(event); - return; - } - - try { - finset.removePoint(index); - } catch (IllegalFinPointException ignore) { - } - } - + 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 + try { + finset.removePoint(clickIndex); + } catch (IllegalFinPointException ignore) { + log.error("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + ". This is likely an internal error."); + } + return; + } + } + + super.mouseClicked(event); + } private int getPoint(MouseEvent event) { Point p0 = event.getPoint(); @@ -507,28 +487,10 @@ public class FreeformFinSetConfig extends FinSetConfig { return figure.convertPoint(x, y); } - } - - - private enum Columns { - // NUMBER { - // @Override - // public String toString() { - // return "#"; - // } - // @Override - // public String getValue(FreeformFinSet finset, int row) { - // return "" + (row+1) + "."; - // } - // @Override - // public int getWidth() { - // return 10; - // } - // }, X { @Override public String toString() { @@ -563,11 +525,6 @@ public class FreeformFinSetConfig extends FinSetConfig { } private class FinPointTableModel extends AbstractTableModel { - - /** - * - */ - private static final long serialVersionUID = 4803736958177227852L; @Override public int getColumnCount() { @@ -603,6 +560,7 @@ public class FreeformFinSetConfig extends FinSetConfig { if (!(o instanceof String)) return; + // bounds check that indices are valid if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || columnIndex < 0 || columnIndex >= Columns.values().length) { throw new IllegalArgumentException("Index out of bounds, row=" + rowIndex + " column=" + columnIndex + " fin point count=" + finset.getFinPoints().length); } @@ -612,15 +570,19 @@ public class FreeformFinSetConfig extends FinSetConfig { double value = UnitGroup.UNITS_LENGTH.fromString(str); Coordinate c = finset.getFinPoints()[rowIndex]; - if (columnIndex == Columns.X.ordinal()) + if (columnIndex == Columns.X.ordinal()){ c = c.setX(value); - else + }else{ c = c.setY(value); - + } + finset.setPoint(rowIndex, c.x, c.y); + updateFields(); } catch (NumberFormatException ignore) { + log.warn("ignoring NumberFormatException while editing a Freeform Fin"); } catch (IllegalFinPointException ignore) { + log.warn("ignoring IllegalFinPointException while editing a Freeform Fin"); } } } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index 9de060940..69dd02966 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -77,7 +77,7 @@ public class RocketComponentConfig extends JPanel { public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) { - setLayout(new MigLayout("fill", "[min,align right]:10[fill, grow]")); + setLayout(new MigLayout("fill, gap 5!, ins panel", "[]:5[]", "[growprio 10]10![fill, grow, growprio 500]10![growprio 10]")); this.document = document; this.component = component; @@ -85,7 +85,7 @@ public class RocketComponentConfig extends JPanel { JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname")); //// The component name. label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname")); - this.add(label, "spanx, split"); + this.add(label, "spanx, height 50!, split"); componentNameField = new JTextField(15); textFieldListener = new TextFieldListener(); @@ -106,7 +106,7 @@ public class RocketComponentConfig extends JPanel { tabbedPane = new JTabbedPane(); - this.add(tabbedPane, "newline, span, growx, growy 1, wrap"); + this.add(tabbedPane, "newline, span, growx, growy 100, wrap"); //// Override and Mass and CG override options tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(), @@ -132,7 +132,7 @@ public class RocketComponentConfig extends JPanel { this.remove(buttonPanel); } - buttonPanel = new JPanel(new MigLayout("fill, ins 0")); + buttonPanel = new JPanel(new MigLayout("fillx, ins 0")); //// Mass: infoLabel = new StyledLabel(" ", -1); @@ -154,7 +154,7 @@ public class RocketComponentConfig extends JPanel { updateFields(); - this.add(buttonPanel, "spanx, growx"); + this.add(buttonPanel, "newline, spanx, growx, height 50!"); } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java index 33ec67823..14cda1d8d 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java @@ -506,7 +506,6 @@ public class GeneralOptimizationDialog extends JDialog { // // Rocket figure figure = new RocketFigure( getSelectedSimulation().getRocket() ); - figure.setBorderPixels(1, 1); ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure); figureScrollPane.setFitting(true); panel.add(figureScrollPane, "span, split, height 200lp, grow"); diff --git a/swing/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java index 0a56cce7d..d95f9fb8f 100644 --- a/swing/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/swing/src/net/sf/openrocket/gui/print/DesignReport.java @@ -177,7 +177,7 @@ public class DesignReport { canvas.beginText(); canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE); - int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS + int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getHeight()) * 0.4 * (scale / PrintUnit.METERS .toPoints(1))); final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts); canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight); @@ -274,7 +274,7 @@ public class DesignReport { theFigure.updateFigure(); double scale = - (thePageImageableWidth * 2.2) / theFigure.getFigureWidth(); + (thePageImageableWidth * 2.2) / theFigure.getWidth(); theFigure.setScale(scale); /* * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion @@ -288,7 +288,7 @@ public class DesignReport { int y = PrintUnit.POINTS_PER_INCH; //If the y dimension is negative, then it will potentially be drawn off the top of the page. Move the origin //to allow for this. - if (theFigure.getDimensions().getY() < 0.0d) { + if (theFigure.getHeight() < 0.0d) { y += (int) halfFigureHeight; } g2d.translate(20, y); diff --git a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java index 13661be97..344713505 100644 --- a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java +++ b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java @@ -22,18 +22,12 @@ public class PrintFigure extends RocketFigure { super(rkt); } - @Override - protected double computeTy(int heightPx) { - super.computeTy(heightPx); - return 0; - } - public void setScale(final double theScale) { this.scale = theScale; //dpi/0.0254*scaling; updateFigure(); } public double getFigureHeightPx() { - return this.figureHeightPx; + return this.getSize().height; } } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java index 7a0f2e335..6602e462b 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java @@ -34,9 +34,9 @@ public class FinSetShapes extends RocketComponentShape { Coordinate c = finSetFront.add(finPoints[i]); if (i==0) - p.moveTo(c.x*S, c.y*S); + p.moveTo(c.x, c.y); else - p.lineTo(c.x*S, c.y*S); + p.lineTo(c.x, c.y); } p.closePath(); @@ -87,13 +87,13 @@ public class FinSetShapes extends RocketComponentShape { Path2D.Double p = new Path2D.Double(); a = finFront.add( c[0] ); - p.moveTo(a.z*S, a.y*S); + p.moveTo(a.z, a.y); a = finFront.add( c[1] ); - p.lineTo(a.z*S, a.y*S); + p.lineTo(a.z, a.y); a = finFront.add( c[2] ); - p.lineTo(a.z*S, a.y*S); + p.lineTo(a.z, a.y); a = finFront.add( c[3] ); - p.lineTo(a.z*S, a.y*S); + p.lineTo(a.z, a.y); p.closePath(); return new Shape[]{p}; @@ -190,9 +190,9 @@ public class FinSetShapes extends RocketComponentShape { for (int i=0; i < array.length; i++) { Coordinate a = t.transform(compCenter.add( array[i]) ); if (i==0) - p.moveTo(a.z*S, a.y*S); + p.moveTo(a.z, a.y); else - p.lineTo(a.z*S, a.y*S); + p.lineTo(a.z, a.y); } p.closePath(); return p; diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java index 788872801..2742d959d 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/MassComponentShapes.java @@ -30,8 +30,7 @@ public class MassComponentShapes extends RocketComponentShape { Coordinate start = transformation.transform( componentAbsoluteLocation); Shape[] s = new Shape[1]; - s[0] = new RoundRectangle2D.Double(start.x*S,(start.y-radius)*S, - length*S,2*radius*S,arc*S,arc*S); + s[0] = new RoundRectangle2D.Double(start.x, (start.y-radius), length, 2*radius, arc, arc); switch (type) { case ALTIMETER: @@ -75,7 +74,7 @@ public class MassComponentShapes extends RocketComponentShape { Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { - s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); + s[i] = new Ellipse2D.Double((start[i].z-or),(start[i].y-or),2*or,2*or); } return RocketComponentShape.toArray(s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java index 38a87437c..c66f66699 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java @@ -23,8 +23,8 @@ public class MassObjectShapes extends RocketComponentShape { Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { - s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S, - length*S,2*radius*S,arc*S,arc*S); + s[i] = new RoundRectangle2D.Double(start[i].x,(start[i].y-radius), + length,2*radius,arc,arc); } return RocketComponentShape.toArray(s, component); @@ -44,7 +44,7 @@ public class MassObjectShapes extends RocketComponentShape { Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { - s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); + s[i] = new Ellipse2D.Double((start[i].z-or),(start[i].y-or),2*or,2*or); } return RocketComponentShape.toArray(s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ParachuteShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ParachuteShapes.java index 49f88ee71..bbfbeb503 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/ParachuteShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ParachuteShapes.java @@ -26,8 +26,7 @@ public class ParachuteShapes extends RocketComponentShape { Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { - s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S, - length*S,2*radius*S,arc*S,arc*S); + s[i] = new RoundRectangle2D.Double(start[i].x, (start[i].y-radius), length, 2*radius, arc, arc); } return RocketComponentShape.toArray( addSymbol(s), component); } @@ -46,7 +45,7 @@ public class ParachuteShapes extends RocketComponentShape { Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { - s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); + s[i] = new Ellipse2D.Double((start[i].z-or),(start[i].y-or),2*or,2*or); } return RocketComponentShape.toArray( s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/RailButtonShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RailButtonShapes.java index 0222656e5..4abcc49bc 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/RailButtonShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/RailButtonShapes.java @@ -43,12 +43,12 @@ public class RailButtonShapes extends RocketComponentShape { final double drawHeight = outerDiameter*sinr; final Point2D.Double center = new Point2D.Double( instanceAbsoluteLocation.x, instanceAbsoluteLocation.y ); Point2D.Double lowerLeft = new Point2D.Double( center.x - outerRadius, center.y-outerRadius*sinr); - path.append( new Ellipse2D.Double( lowerLeft.x*S, lowerLeft.y*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, lowerLeft.y, drawWidth, drawHeight), false); - path.append( new Line2D.Double( lowerLeft.x*S, center.y*S, lowerLeft.x*S, (center.y+baseHeightcos)*S ), false); - path.append( new Line2D.Double( (center.x+outerRadius)*S, center.y*S, (center.x+outerRadius)*S, (center.y+baseHeightcos)*S ), false); + path.append( new Line2D.Double( lowerLeft.x, center.y, lowerLeft.x, (center.y+baseHeightcos) ), false); + path.append( new Line2D.Double( (center.x+outerRadius), center.y, (center.x+outerRadius), (center.y+baseHeightcos) ), false); - path.append( new Ellipse2D.Double( lowerLeft.x*S, (lowerLeft.y+baseHeightcos)*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, (lowerLeft.y+baseHeightcos), drawWidth, drawHeight), false); } {// inner @@ -56,24 +56,24 @@ public class RailButtonShapes extends RocketComponentShape { final double drawHeight = innerDiameter*sinr; final Point2D.Double center = new Point2D.Double( instanceAbsoluteLocation.x, instanceAbsoluteLocation.y + baseHeightcos); final Point2D.Double lowerLeft = new Point2D.Double( center.x - innerRadius, center.y-innerRadius*sinr); - path.append( new Ellipse2D.Double( lowerLeft.x*S, lowerLeft.y*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, lowerLeft.y, drawWidth, drawHeight), false); - path.append( new Line2D.Double( lowerLeft.x*S, center.y*S, lowerLeft.x*S, (center.y+innerHeightcos)*S ), false); - path.append( new Line2D.Double( (center.x+innerRadius)*S, center.y*S, (center.x+innerRadius)*S, (center.y+innerHeightcos)*S ), false); + path.append( new Line2D.Double( lowerLeft.x, center.y, lowerLeft.x, (center.y+innerHeightcos) ), false); + path.append( new Line2D.Double( (center.x+innerRadius), center.y, (center.x+innerRadius), (center.y+innerHeightcos) ), false); - path.append( new Ellipse2D.Double( lowerLeft.x*S, (lowerLeft.y+innerHeightcos)*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, (lowerLeft.y+innerHeightcos), drawWidth, drawHeight), false); } {// outer flange final double drawWidth = outerDiameter; final double drawHeight = outerDiameter*sinr; final Point2D.Double center = new Point2D.Double( instanceAbsoluteLocation.x, instanceAbsoluteLocation.y+baseHeightcos+innerHeightcos); final Point2D.Double lowerLeft = new Point2D.Double( center.x - outerRadius, center.y-outerRadius*sinr); - path.append( new Ellipse2D.Double( lowerLeft.x*S, lowerLeft.y*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, lowerLeft.y, drawWidth, drawHeight), false); - path.append( new Line2D.Double( lowerLeft.x*S, center.y*S, lowerLeft.x*S, (center.y+flangeHeightcos)*S ), false); - path.append( new Line2D.Double( (center.x+outerRadius)*S, center.y*S, (center.x+outerRadius)*S, (center.y+flangeHeightcos)*S ), false); + path.append( new Line2D.Double( lowerLeft.x, center.y, lowerLeft.x, (center.y+flangeHeightcos) ), false); + path.append( new Line2D.Double( (center.x+outerRadius), center.y, (center.x+outerRadius), (center.y+flangeHeightcos) ), false); - path.append( new Ellipse2D.Double( lowerLeft.x*S, (lowerLeft.y+flangeHeightcos)*S, drawWidth*S, drawHeight*S), false); + path.append( new Ellipse2D.Double( lowerLeft.x, (lowerLeft.y+flangeHeightcos), drawWidth, drawHeight), false); } return RocketComponentShape.toArray( new Shape[]{ path }, component ); @@ -131,10 +131,10 @@ public class RailButtonShapes extends RocketComponentShape { final double sinr = Math.sin(angle_rad); final double cosr = Math.cos(angle_rad); - rect.moveTo( (x-radius*cosr)*S, (y+radius*sinr)*S); - rect.lineTo( (x-radius*cosr+height*sinr)*S, (y+radius*sinr+height*cosr)*S); - rect.lineTo( (x+radius*cosr+height*sinr)*S, (y-radius*sinr+height*cosr)*S); - rect.lineTo( (x+radius*cosr)*S, (y-radius*sinr)*S); + rect.moveTo( (x-radius*cosr), (y+radius*sinr)); + rect.lineTo( (x-radius*cosr+height*sinr), (y+radius*sinr+height*cosr)); + rect.lineTo( (x+radius*cosr+height*sinr), (y-radius*sinr+height*cosr)); + rect.lineTo( (x+radius*cosr), (y-radius*sinr)); rect.closePath(); // add points diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java index d3c2fbd52..be1ccc916 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java @@ -16,8 +16,6 @@ import net.sf.openrocket.util.Transformation; */ public class RocketComponentShape { - protected static final double S = RocketFigure.EXTRA_SCALE; - final public boolean hasShape; final public Shape shape; final public net.sf.openrocket.util.Color color; diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java index 58396a4b1..7387399b5 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ShockCordShapes.java @@ -25,8 +25,8 @@ public class ShockCordShapes extends RocketComponentShape { Coordinate start = transformation.transform( componentAbsoluteLocation); Shape[] s = new Shape[1]; - s[0] = new RoundRectangle2D.Double(start.x*S,(start.y-radius)*S, - length*S,2*radius*S,arc*S,arc*S); + s[0] = new RoundRectangle2D.Double(start.x,(start.y-radius), + length,2*radius,arc,arc); return RocketComponentShape.toArray( addSymbol(s), component); } @@ -43,13 +43,13 @@ public class ShockCordShapes extends RocketComponentShape { Shape[] s = new Shape[1]; Coordinate start = componentAbsoluteLocation; - s[0] = new Ellipse2D.Double((start.z-or)*S,(start.y-or)*S,2*or*S,2*or*S); + s[0] = new Ellipse2D.Double((start.z-or),(start.y-or),2*or,2*or); // Coordinate[] start = transformation.transform(tube.toAbsolute(instanceOffset)); // // Shape[] s = new Shape[start.length]; // for (int i=0; i < start.length; i++) { -// s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); +// s[i] = new Ellipse2D.Double((start[i].z-or),(start[i].y-or),2*or,2*or); // } return RocketComponentShape.toArray( s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java index 28bec20cc..480e8d958 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/StreamerShapes.java @@ -24,14 +24,14 @@ public class StreamerShapes extends RocketComponentShape { Shape[] s = new Shape[1]; Coordinate frontCenter = componentAbsoluteLocation; - s[0] = new RoundRectangle2D.Double((frontCenter.x)*S,(frontCenter.y-radius)*S, - length*S,2*radius*S,arc*S,arc*S); + s[0] = new RoundRectangle2D.Double((frontCenter.x),(frontCenter.y-radius), + length,2*radius,arc,arc); // Coordinate[] start = transformation.transform(tube.toAbsolute(instanceOffset)); // Shape[] s = new Shape[start.length]; // for (int i=0; i < start.length; i++) { -// s[i] = new RoundRectangle2D.Double(start[i].x*S,(start[i].y-radius)*S, -// length*S,2*radius*S,arc*S,arc*S); +// s[i] = new RoundRectangle2D.Double(start[i].x,(start[i].y-radius), +// length,2*radius,arc,arc); // } return RocketComponentShape.toArray(addSymbol(s), component); } @@ -47,13 +47,13 @@ public class StreamerShapes extends RocketComponentShape { double or = tube.getRadius(); Shape[] s = new Shape[1]; Coordinate center = componentAbsoluteLocation; - s[0] = new Ellipse2D.Double((center.z-or)*S,(center.y-or)*S,2*or*S,2*or*S); + s[0] = new Ellipse2D.Double((center.z-or),(center.y-or),2*or,2*or); // Coordinate[] start = transformation.transform(tube.toAbsolute(instanceOffset)); // // Shape[] s = new Shape[start.length]; // for (int i=0; i < start.length; i++) { -// s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); +// s[i] = new Ellipse2D.Double((start[i].z-or),(start[i].y-or),2*or,2*or); // } return RocketComponentShape.toArray(s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java index c08fd49ac..e03a94b4d 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.rocketfigure; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; @@ -21,15 +22,8 @@ public class SymmetricComponentShapes extends RocketComponentShape { Transformation transformation, Coordinate componentAbsoluteLocation) { - return getShapesSide(component, transformation, componentAbsoluteLocation, S); - } - - public static RocketComponentShape[] getShapesSide( - net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation, - Coordinate componentAbsoluteLocation, - final double scaleFactor) { - net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent) component; + SymmetricComponent c = (SymmetricComponent) component; + int i; final double delta = 0.0000001; @@ -89,14 +83,14 @@ public class SymmetricComponentShapes extends RocketComponentShape { // TODO: LOW: curved path instead of linear Path2D.Double path = new Path2D.Double(); - path.moveTo((nose.x + points.get(len - 1).x) * scaleFactor, (nose.y+points.get(len - 1).y) * scaleFactor); + path.moveTo((nose.x + points.get(len - 1).x) , (nose.y+points.get(len - 1).y) ); for (i = len - 2; i >= 0; i--) { - path.lineTo((nose.x+points.get(i).x)* scaleFactor, (nose.y+points.get(i).y) * scaleFactor); + path.lineTo((nose.x+points.get(i).x), (nose.y+points.get(i).y) ); } for (i = 0; i < len; i++) { - path.lineTo((nose.x+points.get(i).x) * scaleFactor, (nose.y-points.get(i).y) * scaleFactor); + path.lineTo((nose.x+points.get(i).x) , (nose.y-points.get(i).y) ); } - path.lineTo((nose.x+points.get(len - 1).x) * scaleFactor, (nose.y+points.get(len - 1).y) * scaleFactor); + path.lineTo((nose.x+points.get(len - 1).x) , (nose.y+points.get(len - 1).y) ); path.closePath(); //s[len] = path; diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java index 5a937ac79..e47db5fa2 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java @@ -8,7 +8,6 @@ import net.sf.openrocket.util.Transformation; import java.awt.*; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; -import java.awt.geom.Rectangle2D; public class TransitionShapes extends RocketComponentShape { @@ -16,10 +15,10 @@ public class TransitionShapes extends RocketComponentShape { // TODO: LOW: Uses only first component of cluster (not currently clusterable). public static RocketComponentShape[] getShapesSide( - net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation, - Coordinate instanceLocation) { - return getShapesSide(component, transformation, instanceLocation, S); + RocketComponent component, + Transformation transformation, + Coordinate instanceLocation) { + return getShapesSide(component, transformation, instanceLocation, 1.0); } public static RocketComponentShape[] getShapesSide( @@ -27,7 +26,8 @@ public class TransitionShapes extends RocketComponentShape { Transformation transformation, Coordinate instanceAbsoluteLocation, final double scaleFactor) { - + + Transition transition = (Transition)component; RocketComponentShape[] mainShapes; @@ -41,15 +41,15 @@ public class TransitionShapes extends RocketComponentShape { double r2 = transition.getAftRadius(); Path2D.Float path = new Path2D.Float(); - path.moveTo( (frontCenter.x)* scaleFactor, (frontCenter.y+ r1)* scaleFactor); - path.lineTo( (frontCenter.x+length)* scaleFactor, (frontCenter.y+r2)* scaleFactor); - path.lineTo( (frontCenter.x+length)* scaleFactor, (frontCenter.y-r2)* scaleFactor); - path.lineTo( (frontCenter.x)* scaleFactor, (frontCenter.y-r1)* scaleFactor); + path.moveTo( (frontCenter.x), (frontCenter.y+ r1)); + path.lineTo( (frontCenter.x+length), (frontCenter.y+r2)); + path.lineTo( (frontCenter.x+length), (frontCenter.y-r2)); + path.lineTo( (frontCenter.x), (frontCenter.y-r1)); path.closePath(); mainShapes = new RocketComponentShape[] { new RocketComponentShape( path, component) }; } else { - mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, instanceAbsoluteLocation, scaleFactor); + mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, instanceAbsoluteLocation); } Shape foreShoulder=null, aftShoulder=null; @@ -105,8 +105,8 @@ public class TransitionShapes extends RocketComponentShape { Coordinate center = componentAbsoluteLocation; Shape[] s = new Shape[2]; - s[0] = new Ellipse2D.Double((center.z-r1)*S,(center.y-r1)*S,2*r1*S,2*r1*S); - s[1] = new Ellipse2D.Double((center.z-r2)*S,(center.y-r2)*S,2*r2*S,2*r2*S); + s[0] = new Ellipse2D.Double((center.z-r1),(center.y-r1),2*r1,2*r1); + s[1] = new Ellipse2D.Double((center.z-r2),(center.y-r2),2*r2,2*r2); return RocketComponentShape.toArray(s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/TubeFinSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/TubeFinSetShapes.java index 44a34bc05..5d6b56323 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/TubeFinSetShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/TubeFinSetShapes.java @@ -40,7 +40,7 @@ public class TubeFinSetShapes extends RocketComponentShape { Shape[] s = new Shape[fins]; for (int i=0; i listeners = new LinkedList(); public AbstractScaleFigure() { - this.dpi = GUIUtil.getDPI(); - this.scaling = 1.0; - this.scale = dpi / 0.0254 * scaling; - + // produces a pixels-per-meter scale factor + // + // dots dots inch + // ---- = ------ * ----- + // meter inch meter + // + this.baseScale = GUIUtil.getDPI() * INCHES_PER_METER; + this.userScale = 1.0; + this.scale = baseScale * userScale; + + this.setPreferredSize(new Dimension(100,100)); + setSize(100,100); + setBackground(Color.WHITE); setOpaque(true); } - - - public abstract void updateFigure(); - - public abstract double getFigureWidth(); - - public abstract double getFigureHeight(); - - - @Override - public double getScaling() { - return scaling; + public double getUserScale(){ + return userScale; } - @Override - public double getAbsoluteScale() { - return scale; + public double getAbsoluteScale() { + return scale; + } + + public Dimension getSubjectOrigin() { + return originLocation_px; + } + + /** + * Set the scale level of the figure. A scale value of 1.0 is equivalent to 100 % scale. + * Smaller scales display the subject smaller. + * + * If the figure would be smaller than the 'visibleBounds', then the figure is grown to match, + * and the figures internal contents are centered according to the figure's origin. + * + * @param newScaleRequest the scale level + * @param visibleBounds the visible bounds upon the Figure + */ + public void scaleTo(final double newScaleRequest, final Dimension visibleBounds) { + if (MathUtil.equals(this.userScale, newScaleRequest, 0.01)){ + return;} + if (Double.isInfinite(newScaleRequest) || Double.isNaN(newScaleRequest)) { + return;} + + log.warn(String.format("scaling Request from %g => %g @%s\n", this.userScale, newScaleRequest, this.getClass().getSimpleName()), new Throwable()); + + this.userScale = MathUtil.clamp( newScaleRequest, MINIMUM_ZOOM, MAXIMUM_ZOOM); + this.scale = baseScale * userScale; + + this.visibleBounds_px = visibleBounds; + + this.fireChangeEvent(); } - @Override - public void setScaling(double scaling) { - if (Double.isInfinite(scaling) || Double.isNaN(scaling)) - scaling = 1.0; - if (scaling < 0.001) - scaling = 0.001; - if (scaling > 1000) - scaling = 1000; - if (Math.abs(this.scaling - scaling) < 0.01) - return; - this.scaling = scaling; - this.scale = dpi / 0.0254 * scaling; - updateFigure(); - } - - @Override - public void setScaling(Dimension bounds) { - double zh = 1, zv = 1; - int w = bounds.width - 2 * borderPixelsWidth - 20; - int h = bounds.height - 2 * borderPixelsHeight - 20; - - if (w < 10) - w = 10; - if (h < 10) - h = 10; - - zh = (w) / getFigureWidth(); - zv = (h) / getFigureHeight(); - - double s = Math.min(zh, zv) / dpi * 0.0254 - 0.001; - - // Restrict to 100% - if (s > 1.0) { - s = 1.0; + /** + * Set the scale level to display newBounds + * + * @param visibleBounds the visible bounds to scale this figure to. + */ + public void scaleTo(Dimension visibleBounds) { + if( 0 == visibleBounds.getWidth() || 0 == visibleBounds.getHeight()) + return; + + updateSubjectDimensions(); + + // dimensions within the viewable area, which are available to draw + final int drawable_width_px = visibleBounds.width - 2 * borderThickness_px.width; + final int drawable_height_px = visibleBounds.height - 2 * borderThickness_px.height; + + if(( 0 < drawable_width_px ) && ( 0 < drawable_height_px)) { + final double width_scale = (drawable_width_px) / ( subjectBounds_m.getWidth() * baseScale); + final double height_scale = (drawable_height_px) / ( subjectBounds_m.getHeight() * baseScale); + final double minScale = Math.min(height_scale, width_scale); + + scaleTo(minScale, visibleBounds); } + } + + /** + * Return the pixel coordinates of the subject's origin. + * + * @return the pixel coordinates of the figure origin. + */ + protected abstract void updateSubjectDimensions(); - setScaling(s); + protected abstract void updateCanvasOrigin(); + + /** + * update preferred figure Size + + */ + protected void updateCanvasSize() { + final int desiredWidth = Math.max((int)this.visibleBounds_px.getWidth(), + (int)(subjectBounds_m.getWidth()*scale) + 2*borderThickness_px.width); + final int desiredHeight = Math.max((int)this.visibleBounds_px.getHeight(), + (int)(subjectBounds_m.getHeight()*scale) + 2*borderThickness_px.height); + + Dimension preferredFigureSize_px = new Dimension(desiredWidth, desiredHeight); + + setPreferredSize(preferredFigureSize_px); + setMinimumSize(preferredFigureSize_px); + } + + protected void updateTransform(){ + // Calculate and store the transformation used + // (inverse is used in detecting clicks on objects) + projection = new AffineTransform(); + projection.translate(this.originLocation_px.width, originLocation_px.height); + // Mirror position Y-axis upwards + projection.scale(scale, -scale); + } + + /** + * Updates the figure shapes and figure size. + */ + public void updateFigure() { + log.debug(String.format("____ Updating %s to: %g user scale, %g overall scale", this.getClass().getSimpleName(), this.getAbsoluteScale(), this.scale)); + + updateSubjectDimensions(); + updateCanvasSize(); + updateCanvasOrigin(); + updateTransform(); + + revalidate(); + repaint(); + } + + protected Dimension getBorderPixels() { + return borderThickness_px; } - - - @Override - public Dimension getBorderPixels() { - return new Dimension(borderPixelsWidth, borderPixelsHeight); - } - - @Override - public void setBorderPixels(int width, int height) { - this.borderPixelsWidth = width; - this.borderPixelsHeight = height; - } - - - @Override + public void addChangeListener(StateChangeListener listener) { listeners.add(0, listener); } - @Override public void removeChangeListener(StateChangeListener listener) { listeners.remove(listener); } - private EventObject changeEvent = null; - protected void fireChangeEvent() { - if (changeEvent == null) - changeEvent = new EventObject(this); + final EventObject changeEvent = new EventObject(this); + // Copy the list before iterating to prevent concurrent modification exceptions. EventListener[] list = listeners.toArray(new EventListener[0]); for (EventListener l : list) { @@ -135,5 +207,5 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure } } } - + } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java index b6fdd2bef..46afee80a 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java @@ -7,102 +7,79 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.LinkedList; +import java.util.List; +import org.slf4j.*; import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.unit.Tick; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.StateChangeListener; -// TODO: MEDIUM: the figure jumps and bugs when using automatic fitting - @SuppressWarnings("serial") public class FinPointFigure extends AbstractScaleFigure { - - private static final int BOX_SIZE = 4; - - private final FreeformFinSet finset; + + private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class); + + private static final Color GRID_LINE_COLOR = new Color( 137, 137, 137, 32); + private static final float GRID_LINE_BASE_WIDTH = 0.001f; + + private static final int LINE_WIDTH_PIXELS = 1; + + // the size of the boxes around each fin point vertex + private static final float BOX_WIDTH_PIXELS = 12; + private static final float SELECTED_BOX_WIDTH_PIXELS = BOX_WIDTH_PIXELS + 4; + private static final Color POINT_COLOR = new Color(100, 100, 100); + private static final Color SELECTED_POINT_COLOR = new Color(200, 0, 0); + private static final double MINOR_TICKS = 0.05; + private static final double MAJOR_TICKS = 0.1; + + private final FreeformFinSet finset; private int modID = -1; - private double minX, maxX, maxY; - private double figureWidth = 0; - private double figureHeight = 0; - private double translateX = 0; - private double translateY = 0; - - private AffineTransform transform; - private Rectangle2D.Double[] handles = null; + protected Rectangle2D finBounds_m = null; + protected Rectangle2D mountBounds_m = null; + protected final List listeners = new LinkedList(); + + private Rectangle2D.Double[] finPointHandles = null; + private int selectedIndex = -1; public FinPointFigure(FreeformFinSet finset) { this.finset = finset; + + // useful for debugging -- shows a contrast against un-drawn space. + setBackground(Color.WHITE); + setOpaque(true); + + updateFigure(); } - - + @Override public void paintComponent(Graphics g) { super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - - if (modID != finset.getRocket().getAerodynamicModID()) { - modID = finset.getRocket().getAerodynamicModID(); - calculateDimensions(); - } - - - double tx, ty; - // Calculate translation for figure centering - if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) { - - // Figure fits in the viewport - tx = (getWidth() - figureWidth * scale) / 2 - minX * scale; - - } else { - - // Figure does not fit in viewport - tx = borderPixelsWidth - minX * scale; - - } - - - if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) { - ty = getHeight() - borderPixelsHeight; - } else { - ty = borderPixelsHeight + figureHeight * scale; - } - - if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) { - // Origin has changed, fire event - translateX = tx; - translateY = ty; - fireChangeEvent(); - } - - - if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) { - // Origin has changed, fire event - translateX = tx; - translateY = ty; - fireChangeEvent(); - } - - - // Calculate and store the transformation used - transform = new AffineTransform(); - transform.translate(translateX, translateY); - transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE); - - // TODO: HIGH: border Y-scale upwards - - g2.transform(transform); + Graphics2D g2 = (Graphics2D) g.create(); + + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + updateTransform(); + } + + g2.transform(projection); // Set rendering hints appropriately g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, @@ -113,131 +90,211 @@ public class FinPointFigure extends AbstractScaleFigure { RenderingHints.VALUE_ANTIALIAS_ON); + // Background grid + paintBackgroundGrid( g2); - Rectangle visible = g2.getClipBounds(); - double x0 = ((double) visible.x - 3) / EXTRA_SCALE; - double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE; - double y0 = ((double) visible.y - 3) / EXTRA_SCALE; - double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE; - - - // Background grid - - g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale), - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(new Color(0, 0, 255, 30)); - - Unit unit; - if (this.getParent() != null && - this.getParent().getParent() instanceof ScaleScrollPane) { - unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit(); - } else { - unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); - } - - // vertical - Tick[] ticks = unit.getTicks(x0, x1, - ScaleScrollPane.MINOR_TICKS / scale, - ScaleScrollPane.MAJOR_TICKS / scale); - Line2D.Double line = new Line2D.Double(); - for (Tick t : ticks) { - if (t.major) { - line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE, - t.value * EXTRA_SCALE, y1 * EXTRA_SCALE); - g2.draw(line); - } - } - - // horizontal - ticks = unit.getTicks(y0, y1, - ScaleScrollPane.MINOR_TICKS / scale, - ScaleScrollPane.MAJOR_TICKS / scale); - for (Tick t : ticks) { - if (t.major) { - line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE, - x1 * EXTRA_SCALE, t.value * EXTRA_SCALE); - g2.draw(line); - } - } - - - - - - // Base rocket line - g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale), - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(Color.GRAY); - - g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0); - - - // Fin shape - Coordinate[] points = finset.getFinPoints(); - Path2D.Double shape = new Path2D.Double(); - shape.moveTo(0, 0); - for (int i = 1; i < points.length; i++) { - shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE); - } - - g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale), - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(Color.BLACK); - g2.draw(shape); - - - // Fin point boxes - g2.setColor(new Color(150, 0, 0)); - double s = BOX_SIZE * EXTRA_SCALE / scale; - handles = new Rectangle2D.Double[points.length]; - for (int i = 0; i < points.length; i++) { - Coordinate c = points[i]; - handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s); - g2.draw(handles[i]); - } + paintRocketBody(g2); + paintFinShape(g2); + paintFinHandles(g2); } - + public void paintBackgroundGrid( Graphics2D g2){ + Rectangle visible = g2.getClipBounds(); + int x0 = visible.x - 3; + int x1 = visible.x + visible.width + 4; + int y0 = visible.y - 3; + int y1 = visible.y + visible.height + 4; - public int getIndexByPoint(double x, double y) { - if (handles == null) - return -1; - - // Calculate point in shapes' coordinates - Point2D.Double p = new Point2D.Double(x, y); - try { - transform.inverseTransform(p, p); - } catch (NoninvertibleTransformException e) { - return -1; - } - - for (int i = 0; i < handles.length; i++) { - if (handles[i].contains(p)) - return i; - } - return -1; + final float grid_line_width = (float)(FinPointFigure.GRID_LINE_BASE_WIDTH/this.scale); + g2.setStroke(new BasicStroke( grid_line_width, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(FinPointFigure.GRID_LINE_COLOR); + + Unit unit; + if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) { + unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit(); + } else { + unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); + } + + // vertical + Tick[] verticalTicks = unit.getTicks(x0, x1, MINOR_TICKS, MAJOR_TICKS); + Line2D.Double line = new Line2D.Double(); + for (Tick t : verticalTicks) { + if (t.major) { + line.setLine( t.value, y0, t.value, y1); + g2.draw(line); + } + } + + // horizontal + Tick[] horizontalTicks = unit.getTicks(y0, y1, MINOR_TICKS, MAJOR_TICKS); + for (Tick t : horizontalTicks) { + if (t.major) { + line.setLine( x0, t.value, x1, t.value); + g2.draw(line); + } + } + } + + private void paintRocketBody( Graphics2D g2){ + RocketComponent comp = finset.getParent(); + if( comp instanceof Transition ){ + paintBodyTransition(g2); + }else{ + paintBodyTube(g2); + } + } + + // NOTE: This function drawns relative to the reference point of the BODY component + // In other words: 0,0 == the front, foreRadius of the body component + private void paintBodyTransition( Graphics2D g2){ + + // setup lines + final float bodyLineWidth = (float) ( LINE_WIDTH_PIXELS / scale ); + g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); + + Transition body = (Transition) finset.getParent(); + final float xResolution_m = 0.01f; // distance between draw points, in meters + + final double xFinStart = finset.asPositionValue(AxialMethod.TOP); //<< in body frame + + // vv in fin-frame == draw-frame vv + final double xOffset = -xFinStart; + final double yOffset = -body.getRadius(xFinStart); + + Path2D.Double bodyShape = new Path2D.Double(); + // draw front-cap: + bodyShape.moveTo( xOffset, yOffset); + bodyShape.lineTo( xOffset, yOffset + body.getForeRadius()); + + final float length_m = (float)( body.getLength()); + Point2D.Double cur = new Point2D.Double (); + for( double xBody = xResolution_m ; xBody < length_m; xBody += xResolution_m ){ + // xBody is distance from front of parent body + cur.x = xOffset + xBody; // offset from origin (front of fin) + cur.y = yOffset + body.getRadius( xBody); // offset from origin ( fin-front-point ) + + bodyShape.lineTo( cur.x, cur.y); + } + + // draw end-cap + bodyShape.lineTo( xOffset + length_m, yOffset + body.getAftRadius()); + bodyShape.lineTo( xOffset + length_m, yOffset); + + g2.draw(bodyShape); + } + + private void paintBodyTube( Graphics2D g2){ + // in-figure left extent + final double xFore = mountBounds_m.getMinX(); + // in-figure right extent + final double xAft = mountBounds_m.getMaxX(); + // in-figure right extent + final double yCenter = mountBounds_m.getMinY(); + + Path2D.Double shape = new Path2D.Double(); + shape.moveTo( xFore, yCenter ); + shape.lineTo( xFore, 0); // body tube fore edge + shape.lineTo( xAft, 0); // body tube side + shape.lineTo( xAft, yCenter); // body tube aft edge + + final float bodyLineWidth = (float) ( LINE_WIDTH_PIXELS / scale ); + g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); + g2.draw(shape); + } + + private void paintFinShape(final Graphics2D g2){ + // excludes fin tab points + final Coordinate[] drawPoints = finset.getFinPoints(); + + Path2D.Double shape = new Path2D.Double(); + Coordinate startPoint= drawPoints[0]; + shape.moveTo( startPoint.x, startPoint.y); + for (int i = 1; i < drawPoints.length; i++) { + shape.lineTo( drawPoints[i].x, drawPoints[i].y); + } + + final float finEdgeWidth_m = (float) (LINE_WIDTH_PIXELS / scale ); + g2.setStroke(new BasicStroke( finEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLUE); + g2.draw(shape); } + private void paintFinHandles(final Graphics2D g2) { + // excludes fin tab points + final Coordinate[] drawPoints = finset.getFinPoints(); + + // Fin point boxes + final float boxWidth = (float) (BOX_WIDTH_PIXELS / scale ); + final float boxHalfWidth = boxWidth/2; + + final float boxEdgeWidth_m = (float) ( LINE_WIDTH_PIXELS / scale ); + g2.setStroke(new BasicStroke( boxEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(POINT_COLOR); + + finPointHandles = new Rectangle2D.Double[ drawPoints.length]; + for (int currentIndex = 0; currentIndex < drawPoints.length; currentIndex++) { + Coordinate c = drawPoints[currentIndex]; + + if( currentIndex == selectedIndex ) { + final float selBoxWidth = (float) (SELECTED_BOX_WIDTH_PIXELS / scale ); + final float selBoxHalfWidth = selBoxWidth/2; + + final Rectangle2D.Double selectedPointHighlight = new Rectangle2D.Double(c.x - selBoxHalfWidth, c.y - selBoxHalfWidth, selBoxWidth, selBoxWidth); + + // switch to the highlight color + g2.setColor(SELECTED_POINT_COLOR); + g2.draw(selectedPointHighlight); + + // reset to the normal color + g2.setColor(POINT_COLOR); + } + + // normal boxes + finPointHandles[currentIndex] = new Rectangle2D.Double(c.x - boxHalfWidth, c.y - boxHalfWidth, boxWidth, boxWidth); + + g2.draw(finPointHandles[currentIndex]); + } + } + + private Point2D.Double getPoint( final int x, final int y){ + if (finPointHandles == null) + return null; + + // Calculate point in shapes' coordinates + Point2D.Double p = new Point2D.Double(x, y); + try { + projection.inverseTransform(p, p); + return p; + } catch (NoninvertibleTransformException e) { + return null; + } + } - public int getSegmentByPoint(double x, double y) { - if (handles == null) - return -1; - - // Calculate point in shapes' coordinates - Point2D.Double p = new Point2D.Double(x, y); - try { - transform.inverseTransform(p, p); - } catch (NoninvertibleTransformException e) { - return -1; - } - - double x0 = p.x / EXTRA_SCALE; - double y0 = p.y / EXTRA_SCALE; - double delta = BOX_SIZE / scale; - - //System.out.println("Point: " + x0 + "," + y0); - //System.out.println("delta: " + (BOX_SIZE / scale)); + public int getIndexByPoint(final int x, final int y) { + final Point2D.Double p = getPoint(x,y); + if (p == null) + return -1; + + for (int i = 0; i < finPointHandles.length; i++) { + if (finPointHandles[i].contains(p)) { + return i; + } + } + + return -1; + } + + public int getSegmentByPoint(final int x, final int y) { + final Point2D.Double p = getPoint(x,y); + if (p == null) + return -1; + + final double threshold = BOX_WIDTH_PIXELS / scale; Coordinate[] points = finset.getFinPoints(); for (int i = 1; i < points.length; i++) { @@ -246,100 +303,92 @@ public class FinPointFigure extends AbstractScaleFigure { double x2 = points[i].x; double y2 = points[i].y; - // System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2); - - double u = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / - MathUtil.hypot(x2 - x1, y2 - y1); - //System.out.println("Distance of segment " + i + " is " + u); - if (u < delta) - return i; + final double segmentLength = MathUtil.hypot(x2 - x1, y2 - y1); + + // Distance to an infinite line, defined by two points: + // (For a more in-depth explanation, see wikipedia: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line ) + double x0 = p.x; + double y0 = p.y; + final double distanceToLine = Math.abs((y2 - y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)/segmentLength; + + final double distanceToStart = MathUtil.hypot(x1-x0, y1-y0); + final double distanceToEnd = MathUtil.hypot(x2-x0, y2-y0); + final boolean withinSegment = (distanceToStart < segmentLength && distanceToEnd < segmentLength); + + if ( distanceToLine < threshold && withinSegment){ + return i; + } + } return -1; } - - public Point2D.Double convertPoint(double x, double y) { + public Point2D.Double convertPoint(final double x, final double y) { Point2D.Double p = new Point2D.Double(x, y); try { - transform.inverseTransform(p, p); + projection.inverseTransform(p, p); } catch (NoninvertibleTransformException e) { assert (false) : "Should not occur"; return new Point2D.Double(0, 0); } - p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE); + p.setLocation(p.x, p.y); return p; } - - - @Override - public Dimension getOrigin() { + public Dimension getSubjectOrigin() { if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); - calculateDimensions(); + updateTransform(); } - return new Dimension((int) translateX, (int) translateY); - } - + return new Dimension(originLocation_px.width, originLocation_px.height); + } + @Override - public double getFigureWidth() { - if (modID != finset.getRocket().getAerodynamicModID()) { - modID = finset.getRocket().getAerodynamicModID(); - calculateDimensions(); - } - return figureWidth; + protected void updateSubjectDimensions(){ + + // update subject (i.e. Fin) bounds + finBounds_m = new BoundingBox().update(finset.getFinPoints()).toRectangle(); + // NOTE: the fin's forward root is pinned at 0,0 + finBounds_m.setRect(0, 0, finBounds_m.getWidth(), finBounds_m.getHeight()); + + // update to bound the parent body: + SymmetricComponent parent = (SymmetricComponent)this.finset.getParent(); + final double xFinFront = finset.asPositionValue(AxialMethod.TOP); + final double xParent = -xFinFront; + final double yParent = -parent.getRadius(xParent); // from parent centerline to fin front. + final double rParent = Math.max(parent.getForeRadius(), parent.getAftRadius()); + mountBounds_m = new Rectangle2D.Double( xParent, yParent, parent.getLength(), rParent); + + final double subjectWidth = Math.max( xFinFront + finBounds_m.getWidth(), parent.getLength()); + final double subjectHeight = Math.max( 2*rParent, rParent + finBounds_m.getHeight()); + subjectBounds_m = new Rectangle2D.Double( xParent, yParent, subjectWidth, subjectHeight); } - - @Override - public double getFigureHeight() { - if (modID != finset.getRocket().getAerodynamicModID()) { - modID = finset.getRocket().getAerodynamicModID(); - calculateDimensions(); - } - return figureHeight; - } - - - private void calculateDimensions() { - minX = 0; - maxX = 0; - maxY = 0; - - for (Coordinate c : finset.getFinPoints()) { - if (c.x < minX) - minX = c.x; - if (c.x > maxX) - maxX = c.x; - if (c.y > maxY) - maxY = c.y; - } - - if (maxX < 0.01) - maxX = 0.01; - - figureWidth = maxX - minX; - figureHeight = maxY; - - - Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth), - (int) (figureHeight * scale + 2 * borderPixelsHeight)); - - if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) { - setPreferredSize(d); - setMinimumSize(d); - revalidate(); - } - } - - @Override - public void updateFigure() { - repaint(); - } - + protected void updateCanvasOrigin() { + final int finHeight = (int)(finBounds_m.getHeight()*scale); + final int mountHeight = (int)(mountBounds_m.getHeight()*scale); + final int finFrontX = (int)(subjectBounds_m.getX()*scale); + final int subjectHeight = (int)(subjectBounds_m.getHeight()*scale); + + // the negative sign is to compensate for the mount's negative location. + originLocation_px.width = borderThickness_px.width - finFrontX; + + if( visibleBounds_px.height > (subjectHeight+ 2*borderThickness_px.height)) { + originLocation_px.height = getHeight() - mountHeight - borderThickness_px.height; + }else { + originLocation_px.height = borderThickness_px.height + finHeight; + } + } + public void resetSelectedIndex() { + this.selectedIndex = -1; + } + + public void setSelectedIndex(final int newIndex) { + this.selectedIndex = newIndex; + } } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index c3a59d65d..d55946e36 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -18,8 +18,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import net.sf.openrocket.gui.figureelements.FigureElement; import net.sf.openrocket.gui.rocketfigure.RocketComponentShape; +import net.sf.openrocket.gui.scalefigure.RocketPanel.VIEW_TYPE; import net.sf.openrocket.gui.util.ColorConversion; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.motor.Motor; @@ -30,6 +34,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LineStyle; @@ -46,6 +51,8 @@ import net.sf.openrocket.util.Transformation; */ @SuppressWarnings("serial") public class RocketFigure extends AbstractScaleFigure { + + private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class); private static final String ROCKET_FIGURE_PACKAGE = "net.sf.openrocket.gui.rocketfigure"; private static final String ROCKET_FIGURE_SUFFIX = "Shapes"; @@ -61,28 +68,17 @@ public class RocketFigure extends AbstractScaleFigure { private Rocket rocket; private RocketComponent[] selection = new RocketComponent[0]; - private double figureWidth = 0, figureHeight = 0; - protected int figureWidthPx = 0, figureHeightPx = 0; private RocketPanel.VIEW_TYPE currentViewType = RocketPanel.VIEW_TYPE.SideView; private double rotation; - private Transformation transformation; - - private double translateX, translateY; - - - + private Transformation axialRotation; + /* * figureComponents contains the corresponding RocketComponents of the figureShapes */ private final ArrayList figureShapes = new ArrayList(); - - private double minX = 0, maxX = 0, maxR = 0; - // Figure width and height in SI-units and pixels - - private AffineTransform g2transformation = null; private final ArrayList relativeExtra = new ArrayList(); private final ArrayList absoluteExtra = new ArrayList(); @@ -96,27 +92,11 @@ public class RocketFigure extends AbstractScaleFigure { this.rocket = _rkt; this.rotation = 0.0; - this.transformation = Transformation.rotate_x(0.0); + this.axialRotation = Transformation.rotate_x(0.0); updateFigure(); } - @Override - public Dimension getOrigin() { - return new Dimension((int) translateX, (int) translateY); - } - - @Override - public double getFigureHeight() { - return figureHeight; - } - - @Override - public double getFigureWidth() { - return figureWidth; - } - - public RocketComponent[] getSelection() { return selection; } @@ -128,6 +108,7 @@ public class RocketFigure extends AbstractScaleFigure { this.selection = selection; } updateFigure(); + fireChangeEvent(); } @@ -136,15 +117,16 @@ public class RocketFigure extends AbstractScaleFigure { } public Transformation getRotateTransformation() { - return transformation; + return axialRotation; } public void setRotation(double rot) { if (MathUtil.equals(rotation, rot)) return; this.rotation = rot; - this.transformation = Transformation.rotate_x(rotation); + this.axialRotation = Transformation.rotate_x(rotation); updateFigure(); + fireChangeEvent(); } @@ -160,25 +142,10 @@ public class RocketFigure extends AbstractScaleFigure { return; this.currentViewType = type; updateFigure(); + fireChangeEvent(); } - /** - * Updates the figure shapes and figure size. - */ - @Override - public void updateFigure() { - figureShapes.clear(); - - calculateSize(); - - getShapeTree( this.figureShapes, rocket, this.transformation, Coordinate.ZERO); - - repaint(); - fireChangeEvent(); - } - - public void addRelativeExtra(FigureElement p) { relativeExtra.add(p); } @@ -219,49 +186,15 @@ public class RocketFigure extends AbstractScaleFigure { AffineTransform baseTransform = g2.getTransform(); - // Update figure shapes if necessary - if (figureShapes == null) - updateFigure(); + updateSubjectDimensions(); + updateCanvasOrigin(); + updateCanvasSize(); + updateTransform(); + + figureShapes.clear(); + updateShapeTree( this.figureShapes, rocket, this.axialRotation, Coordinate.ZERO); - - double tx, ty; - // Calculate translation for figure centering - if (figureWidthPx + 2 * borderPixelsWidth < getWidth()) { - - // Figure fits in the viewport - if (currentViewType == RocketPanel.VIEW_TYPE.BackView){ - tx = getWidth() / 2; - }else{ - tx = (getWidth() - figureWidthPx) / 2 - minX * scale; - } - } else { - - // Figure does not fit in viewport - if (currentViewType == RocketPanel.VIEW_TYPE.BackView){ - tx = borderPixelsWidth + figureWidthPx / 2; - }else{ - tx = borderPixelsWidth - minX * scale; - } - } - - ty = computeTy(figureHeightPx); - - if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) { - // Origin has changed, fire event - translateX = tx; - translateY = ty; - fireChangeEvent(); - } - - - // Calculate and store the transformation used - // (inverse is used in detecting clicks on objects) - g2transformation = new AffineTransform(); - g2transformation.translate(translateX, translateY); - // Mirror position Y-axis upwards - g2transformation.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE); - - g2.transform(g2transformation); + g2.transform(projection); // Set rendering hints appropriately g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, @@ -299,16 +232,16 @@ public class RocketFigure extends AbstractScaleFigure { float[] dashes = style.getDashes(); for (int j = 0; j < dashes.length; j++) { - dashes[j] *= EXTRA_SCALE / scale; + dashes[j] *= 1.0 / scale; } if (selected) { - g2.setStroke(new BasicStroke((float) (SELECTED_WIDTH * EXTRA_SCALE / scale), + g2.setStroke(new BasicStroke((float) (SELECTED_WIDTH / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0)); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); } else { - g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale), + g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0)); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); @@ -316,7 +249,7 @@ public class RocketFigure extends AbstractScaleFigure { g2.draw(rcs.shape); } - g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale), + g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); @@ -346,13 +279,15 @@ public class RocketFigure extends AbstractScaleFigure { { Shape s; if (currentViewType == RocketPanel.VIEW_TYPE.SideView) { - s = new Rectangle2D.Double(EXTRA_SCALE * curMotorLocation.x, - EXTRA_SCALE * (curMotorLocation.y - motorRadius), EXTRA_SCALE * motorLength, - EXTRA_SCALE * 2 * motorRadius); + s = new Rectangle2D.Double( curMotorLocation.x, + (curMotorLocation.y - motorRadius), + motorLength, + 2 * motorRadius); } else { - s = new Ellipse2D.Double(EXTRA_SCALE * (curMotorLocation.z - motorRadius), - EXTRA_SCALE * (curMotorLocation.y - motorRadius), EXTRA_SCALE * 2 * motorRadius, - EXTRA_SCALE * 2 * motorRadius); + s = new Ellipse2D.Double((curMotorLocation.z - motorRadius), + (curMotorLocation.y - motorRadius), + 2 * motorRadius, + 2 * motorRadius); } g2.setColor(fillColor); g2.fill(s); @@ -365,7 +300,7 @@ public class RocketFigure extends AbstractScaleFigure { // Draw relative extras for (FigureElement e : relativeExtra) { - e.paint(g2, scale / EXTRA_SCALE); + e.paint(g2, scale); } // Draw absolute extras @@ -378,22 +313,11 @@ public class RocketFigure extends AbstractScaleFigure { } - protected double computeTy(int heightPx) { - final double ty; - if (heightPx + 2 * borderPixelsHeight < getHeight()) { - ty = getHeight() / 2; - } else { - ty = borderPixelsHeight + heightPx / 2; - } - return ty; - } - - public RocketComponent[] getComponentsByPoint(double x, double y) { // Calculate point in shapes' coordinates Point2D.Double p = new Point2D.Double(x, y); try { - g2transformation.inverseTransform(p, p); + projection.inverseTransform(p, p); } catch (NoninvertibleTransformException e) { return new RocketComponent[0]; } @@ -408,52 +332,54 @@ public class RocketFigure extends AbstractScaleFigure { return l.toArray(new RocketComponent[0]); } - // NOTE: Recursive function - private void getShapeTree( - ArrayList allShapes, // output parameter - final RocketComponent comp, - final Transformation parentTransform, - final Coordinate parentLocation){ + // NOTE: Recursive function + private ArrayList updateShapeTree( + ArrayList allShapes, // output parameter + final RocketComponent comp, + final Transformation parentTransform, + final Coordinate parentLocation){ - - final int instanceCount = comp.getInstanceCount(); - Coordinate[] instanceLocations = comp.getInstanceLocations(); - instanceLocations = parentTransform.transform( instanceLocations ); - double[] instanceAngles = comp.getInstanceAngles(); - if( instanceLocations.length != instanceAngles.length ){ - throw new ArrayIndexOutOfBoundsException(String.format("lengths of location array (%d) and angle arrays (%d) differs! (in: %s) ", instanceLocations.length, instanceAngles.length, comp.getName())); - } - - // iterate over the aggregated instances *for the whole* tree. - for( int index = 0; instanceCount > index ; ++index ){ - final double currentAngle = instanceAngles[index]; - Transformation currentTransform = parentTransform; - if( 0.00001 < Math.abs( currentAngle )) { - Transformation currentAngleTransform = Transformation.rotate_x( currentAngle ); - currentTransform = currentAngleTransform.applyTransformation( parentTransform ); - } - - Coordinate currentLocation = parentLocation.add( instanceLocations[index] ); - -// System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount)); -// System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId())); -// System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString())); -// if( 0.00001 < Math.abs( currentAngle )) { -// System.err.println(String.format(" -- at: %6.4f radians", currentAngle)); -// } - - // generate shape for this component, if active - if( this.rocket.getSelectedConfiguration().isComponentActive( comp )){ - allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform); - } - - // recurse into component's children - for( RocketComponent child: comp.getChildren() ){ - // draw a tree for each instance subcomponent - getShapeTree( allShapes, child, currentTransform, currentLocation ); - } - } + final int instanceCount = comp.getInstanceCount(); + Coordinate[] instanceLocations = comp.getInstanceLocations(); + instanceLocations = parentTransform.transform( instanceLocations ); + double[] instanceAngles = comp.getInstanceAngles(); + if( instanceLocations.length != instanceAngles.length ){ + throw new ArrayIndexOutOfBoundsException(String.format("lengths of location array (%d) and angle arrays (%d) differs! (in: %s) ", instanceLocations.length, instanceAngles.length, comp.getName())); + } + + // iterate over the aggregated instances *for the whole* tree. + for( int index = 0; instanceCount > index ; ++index ){ + final double currentAngle = instanceAngles[index]; + + Transformation currentTransform = parentTransform; + if( 0.00001 < Math.abs( currentAngle )) { + Transformation currentAngleTransform = Transformation.rotate_x( currentAngle ); + currentTransform = currentAngleTransform.applyTransformation( parentTransform ); + } + + Coordinate currentLocation = parentLocation.add( instanceLocations[index] ); + + // System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount)); + // System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId())); + // System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString())); + // if( 0.00001 < Math.abs( currentAngle )) { + // System.err.println(String.format(" -- at: %6.4f radians", currentAngle)); + // } + + // generate shape for this component, if active + if( this.rocket.getSelectedConfiguration().isComponentActive( comp )){ + allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform); + } + + // recurse into component's children + for( RocketComponent child: comp.getChildren() ){ + // draw a tree for each instance subcomponent + updateShapeTree( allShapes, child, currentTransform, currentLocation ); + } + } + + return allShapes; } /** @@ -508,82 +434,52 @@ public class RocketFigure extends AbstractScaleFigure { - /** - * Gets the bounds of the figure, i.e. the maximum extents in the selected dimensions. - * The bounds are stored in the variables minX, maxX and maxR. - */ - private void calculateFigureBounds() { - Collection bounds = rocket.getSelectedConfiguration().getBounds(); - - if (bounds.isEmpty()) { - minX = 0; - maxX = 0; - maxR = 0; - return; - } - - minX = Double.MAX_VALUE; - maxX = Double.MIN_VALUE; - maxR = 0; - for (Coordinate c : bounds) { - double x = c.x, r = MathUtil.hypot(c.y, c.z); - if (x < minX) - minX = x; - if (x > maxX) - maxX = x; - if (r > maxR) - maxR = r; - } - } - -// public double getBestZoom(Rectangle2D bounds) { -// double zh = 1, zv = 1; -// if (bounds.getWidth() > 0.0001) -// zh = (getWidth() - 2 * borderPixelsWidth) / bounds.getWidth(); -// if (bounds.getHeight() > 0.0001) -// zv = (getHeight() - 2 * borderPixelsHeight) / bounds.getHeight(); -// return Math.min(zh, zv); -// } -// - - + /** + * Gets the bounds of the drawn subject in Model-Space + * + * i.e. the maximum extents in the selected dimensions. + * The bounds are stored in the variables minX, maxX and maxR. + * + * @return + */ + @Override + protected void updateSubjectDimensions() { + // calculate bounds, and store in class variables + final BoundingBox bounds = rocket.getSelectedConfiguration().getBoundingBox(); + + switch (currentViewType) { + case SideView: + subjectBounds_m = new Rectangle2D.Double(bounds.min.x, bounds.min.y, bounds.span().x, bounds.span().y); + break; + case BackView: + final double maxR = Math.max(Math.hypot(bounds.min.y, bounds.min.z), Math.hypot(bounds.max.y, bounds.max.z)); + subjectBounds_m = new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR); + break; + default: + throw new BugException("Illegal figure type = " + currentViewType); + } + } + /** * Calculates the necessary size of the figure and set the PreferredSize * property accordingly. */ - private void calculateSize() { - Rectangle2D dimensions = this.getDimensions(); - - figureHeight = dimensions.getHeight(); - figureWidth = dimensions.getWidth(); - - figureWidthPx = (int) (figureWidth * scale); - figureHeightPx = (int) (figureHeight * scale); - - Dimension dpx = new Dimension( - figureWidthPx + 2 * borderPixelsWidth, - figureHeightPx + 2 * borderPixelsHeight); - - if (!dpx.equals(getPreferredSize()) || !dpx.equals(getMinimumSize())) { - setPreferredSize(dpx); - setMinimumSize(dpx); - revalidate(); - } + @Override + protected void updateCanvasOrigin() { + final int subjectWidth = (int)(subjectBounds_m.getWidth()*scale); + final int subjectHeight = (int)(subjectBounds_m.getHeight()*scale); + + if (currentViewType == RocketPanel.VIEW_TYPE.BackView){ + final int newOriginX = borderThickness_px.width + Math.max(getWidth(), subjectWidth + 2*borderThickness_px.width)/ 2; + final int newOriginY = borderThickness_px.height + getHeight() / 2; + + originLocation_px = new Dimension(newOriginX, newOriginY); + }else if (currentViewType == RocketPanel.VIEW_TYPE.SideView){ + final int newOriginX = borderThickness_px.width; + final int newOriginY = Math.max(getHeight(), subjectHeight + 2*borderThickness_px.height )/ 2; + + originLocation_px = new Dimension(newOriginX, newOriginY); + } } - - public Rectangle2D getDimensions() { - calculateFigureBounds(); - - switch (currentViewType) { - case SideView: - return new Rectangle2D.Double(minX, -maxR, maxX - minX, 2 * maxR); - - case BackView: - return new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR); - - default: - throw new BugException("Illegal figure type = " + currentViewType); - } - } - + } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 6e1b0e3a2..8f0bd6208 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -641,8 +641,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change if (figure.getType() == RocketPanel.VIEW_TYPE.SideView && length > 0) { // TODO: LOW: Y-coordinate and rotation - extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0); - extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0); + extraCP.setPosition(cpx, 0); + extraCG.setPosition(cgx, 0); } else { diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java deleted file mode 100644 index 48440fe33..000000000 --- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.sf.openrocket.gui.scalefigure; - -import java.awt.Dimension; - -import net.sf.openrocket.util.ChangeSource; - - -public interface ScaleFigure extends ChangeSource { - - /** - * Extra scaling applied to the figure. The f***ing Java JRE doesn't know - * how to draw shapes when using very large scaling factors, so this must - * be manually applied to every single shape used. - *

- * The scaling factor used is divided by this value, and every coordinate used - * in the figures must be multiplied by this factor. - */ - public static final double EXTRA_SCALE = 1000; - - /** - * Shorthand for {@link #EXTRA_SCALE}. - */ - public static final double S = EXTRA_SCALE; - - - /** - * Set the scale level of the figure. A scale value of 1.0 indicates an original - * size when using the current DPI level. - * - * @param scale the scale level. - */ - public void setScaling(double scale); - - - /** - * Set the scale level so that the figure fits into the given bounds. - * - * @param bounds the bounds of the figure. - */ - public void setScaling(Dimension bounds); - - - /** - * Return the scale level of the figure. A scale value of 1.0 indicates an original - * size when using the current DPI level. - * - * @return the current scale level. - */ - public double getScaling(); - - - /** - * Return the scale of the figure on px/m. - * - * @return the current scale value. - */ - public double getAbsoluteScale(); - - - /** - * Return the pixel coordinates of the figure origin. - * - * @return the pixel coordinates of the figure origin. - */ - public Dimension getOrigin(); - - - /** - * Get the amount of blank space left around the figure. - * - * @return the amount of horizontal and vertical space left on both sides of the figure. - */ - public Dimension getBorderPixels(); - - /** - * Set the amount of blank space left around the figure. - * - * @param width the amount of horizontal space left on both sides of the figure. - * @param height the amount of vertical space left on both sides of the figure. - */ - public void setBorderPixels(int width, int height); -} diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java index a45dd51a8..3851771ff 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java @@ -17,7 +17,6 @@ import java.util.EventObject; import javax.swing.BorderFactory; import javax.swing.JComponent; -import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.event.ChangeEvent; @@ -29,6 +28,7 @@ import net.sf.openrocket.unit.Tick; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.StateChangeListener; @@ -44,6 +44,7 @@ import net.sf.openrocket.util.StateChangeListener; * * @author Sampo Niskanen */ +@SuppressWarnings("serial") public class ScaleScrollPane extends JScrollPane implements MouseListener, MouseMotionListener { @@ -51,45 +52,33 @@ public class ScaleScrollPane extends JScrollPane public static final int MINOR_TICKS = 3; public static final int MAJOR_TICKS = 30; + public static final String USER_SCALE_PROPERTY = "UserScale"; - private JComponent component; - private ScaleFigure figure; + private final JComponent component; + private final AbstractScaleFigure figure; private DoubleModel rulerUnit; private Ruler horizontalRuler; private Ruler verticalRuler; - private final boolean allowFit; - + // is the subject *currently* being fitting private boolean fit = false; - - /** - * Create a scale scroll pane that allows fitting. - * - * @param component the component to contain (must implement ScaleFigure) - */ - public ScaleScrollPane(JComponent component) { - this(component, true); - } - /** * Create a scale scroll pane. * * @param component the component to contain (must implement ScaleFigure) * @param allowFit whether automatic fitting of the figure is allowed */ - public ScaleScrollPane(JComponent component, boolean allowFit) { + public ScaleScrollPane(final JComponent component) { super(component); - if (!(component instanceof ScaleFigure)) { + if (!(component instanceof AbstractScaleFigure)) { throw new IllegalArgumentException("component must implement ScaleFigure"); } this.component = component; - this.figure = (ScaleFigure) component; - this.allowFit = allowFit; - + this.figure = (AbstractScaleFigure) component; rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH); rulerUnit.addChangeListener(new ChangeListener() { @@ -106,50 +95,45 @@ public class ScaleScrollPane extends JScrollPane UnitSelector selector = new UnitSelector(rulerUnit); selector.setFont(new Font("SansSerif", Font.PLAIN, 8)); this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector); - this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel()); - this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel()); - this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel()); this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); - + setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + viewport.addMouseListener(this); viewport.addMouseMotionListener(this); figure.addChangeListener(new StateChangeListener() { @Override public void stateChanged(EventObject e) { - horizontalRuler.updateSize(); + horizontalRuler.updateSize(); verticalRuler.updateSize(); - if (fit) { - setFitting(true); - } + if(fit) { + figure.scaleTo(viewport.getExtentSize()); + } } }); viewport.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { - if (fit) { - setFitting(true); - } + if(fit) { + figure.scaleTo(viewport.getExtentSize()); + } + figure.updateFigure(); + + horizontalRuler.updateSize(); + verticalRuler.updateSize(); } }); } - public ScaleFigure getFigure() { + public AbstractScaleFigure getFigure() { return figure; } - - /** - * Return whether automatic fitting of the figure is allowed. - */ - public boolean isFittingAllowed() { - return allowFit; - } - /** * Return whether the figure is currently automatically fitted within the component bounds. */ @@ -159,54 +143,70 @@ public class ScaleScrollPane extends JScrollPane /** * Set whether the figure is automatically fitted within the component bounds. - * - * @throws BugException if automatic fitting is disallowed and fit is true */ - public void setFitting(boolean fit) { - if (fit && !allowFit) { - throw new BugException("Attempting to fit figure not allowing fit."); - } - this.fit = fit; - if (fit) { - setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + public void setFitting(final boolean shouldFit) { + this.fit = shouldFit; + if (shouldFit) { validate(); - Dimension view = viewport.getExtentSize(); - figure.setScaling(view); - } else { - setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + Dimension view = viewport.getExtentSize(); + figure.scaleTo(view); + this.firePropertyChange( USER_SCALE_PROPERTY, 1.0, figure.getUserScale()); + + revalidate(); } } - - - public double getScaling() { - return figure.getScaling(); + public double getUserScale() { + return figure.getUserScale(); } - public double getScale() { - return figure.getAbsoluteScale(); - } - - public void setScaling(double scale) { - if (fit) { - setFitting(false); - } - figure.setScaling(scale); - horizontalRuler.repaint(); - verticalRuler.repaint(); + public void setScaling(final double newScale) { + // match if closer than 1%: + if( MathUtil.equals(newScale, figure.getUserScale(), 0.01)){ + return; + } + + // if explicitly setting a zoom level, turn off fitting + this.fit = false; + Dimension view = viewport.getExtentSize(); + figure.scaleTo(newScale, view); + + revalidate(); } public Unit getCurrentUnit() { return rulerUnit.getCurrentUnit(); } - + + public String toViewportString(){ + Rectangle view = this.getViewport().getViewRect(); + return ("Viewport::("+view.getWidth()+","+view.getHeight()+")" + +"@("+view.getX()+", "+view.getY()+")"); + } + + @Override + public void revalidate() { + if( null != component ) { + component.revalidate(); + figure.updateFigure(); + } + + if( null != horizontalRuler ){ + horizontalRuler.revalidate(); + horizontalRuler.repaint(); + } + if( null != verticalRuler ){ + verticalRuler.revalidate(); + verticalRuler.repaint(); + } + + super.revalidate(); + } + //////////////// Mouse handlers //////////////// - - private int dragStartX = 0; private int dragStartY = 0; private Rectangle dragRectangle = null; @@ -266,49 +266,47 @@ public class ScaleScrollPane extends JScrollPane public Ruler(int orientation) { this.orientation = orientation; - updateSize(); rulerUnit.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { + updateSize(); Ruler.this.repaint(); } }); } - - public void updateSize() { - Dimension d = component.getPreferredSize(); + private void updateSize() { if (orientation == HORIZONTAL) { - setPreferredSize(new Dimension(d.width + 10, RULER_SIZE)); + Ruler.this.setMinimumSize(new Dimension(component.getWidth() + 10, RULER_SIZE)); + Ruler.this.setPreferredSize(new Dimension(component.getWidth() + 10, RULER_SIZE)); } else { - setPreferredSize(new Dimension(RULER_SIZE, d.height + 10)); + Ruler.this.setMinimumSize(new Dimension(RULER_SIZE, component.getHeight() + 10)); + Ruler.this.setPreferredSize(new Dimension(RULER_SIZE, component.getHeight() + 10)); } revalidate(); repaint(); } - private double fromPx(int px) { - Dimension origin = figure.getOrigin(); - if (orientation == HORIZONTAL) { - px -= origin.width; - } else { - // px = -(px - origin.height); - px -= origin.height; - } - return px / figure.getAbsoluteScale(); + private double fromPx(final int px) { + Dimension origin = figure.getSubjectOrigin(); + double realValue = Double.NaN; + if (orientation == HORIZONTAL) { + realValue = px - origin.width; + } else { + realValue = origin.height - px; + } + return realValue / figure.getAbsoluteScale(); } - private int toPx(double l) { - Dimension origin = figure.getOrigin(); - int px = (int) (l * figure.getAbsoluteScale() + 0.5); + private int toPx(final double value) { + final Dimension origin = figure.getSubjectOrigin(); + final int px = (int) (value * figure.getAbsoluteScale() + 0.5); if (orientation == HORIZONTAL) { - px += origin.width; + return (px + origin.width); } else { - px = px + origin.height; - // px += origin.height; + return (origin.height - px); } - return px; } @@ -317,13 +315,17 @@ public class ScaleScrollPane extends JScrollPane super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; - Rectangle area = g2.getClipBounds(); + updateSize(); + // this function doesn't reliably update all the time, so we'll draw everything for the entire canvas, + // and let the JVM drawing algorithms figure out what should be drawn. + // + Rectangle area = ScaleScrollPane.this.getViewport().getViewRect(); + // Fill area with background color g2.setColor(getBackground()); g2.fillRect(area.x, area.y, area.width, area.height + 100); - - + int startpx, endpx; if (orientation == HORIZONTAL) { startpx = area.x; @@ -333,15 +335,22 @@ public class ScaleScrollPane extends JScrollPane endpx = area.y + area.height; } + final double start = fromPx(startpx); + final double end = fromPx(endpx); + + final double minor = MINOR_TICKS / figure.getAbsoluteScale(); + final double major = MAJOR_TICKS / figure.getAbsoluteScale(); + Unit unit = rulerUnit.getCurrentUnit(); - double start, end, minor, major; - start = fromPx(startpx); - end = fromPx(endpx); - minor = MINOR_TICKS / figure.getAbsoluteScale(); - major = MAJOR_TICKS / figure.getAbsoluteScale(); - - Tick[] ticks = unit.getTicks(start, end, minor, major); - + Tick[] ticks = null; + if( VERTICAL == orientation ){ + // the parameters are *intended* to be backwards: because 'getTicks(...)' can only + // create increasing arrays (where the start < end) + ticks = unit.getTicks(end, start, minor, major); + }else if(HORIZONTAL == orientation ){ + // normal parameter order + ticks = unit.getTicks(start, end, minor, major); + } // Set color & hints g2.setColor(Color.BLACK); diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java index c7c265ed4..a156a544c 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java @@ -4,7 +4,6 @@ import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DecimalFormat; -import java.util.Arrays; import java.util.EventObject; import java.util.Locale; @@ -16,23 +15,27 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.util.StateChangeListener; +@SuppressWarnings("serial") public class ScaleSelector extends JPanel { + public static final double MINIMUM_ZOOM = 0.01; // == 1 % + public static final double MAXIMUM_ZOOM = 1000.00; // == 10,000 % + // Ready zoom settings private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("0.#%"); - private static final double[] ZOOM_LEVELS = { 0.15, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0 }; - private static final String ZOOM_FIT = "Fit"; - private static final String[] ZOOM_SETTINGS; + private static final double[] SCALE_LEVELS = { 0.15, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0 }; + private static final String SCALE_FIT = "Fit"; // trans.get("ScaleSelector.something.something"); + private static final String[] SCALE_LABELS; static { - ZOOM_SETTINGS = new String[ZOOM_LEVELS.length + 1]; - for (int i = 0; i < ZOOM_LEVELS.length; i++) - ZOOM_SETTINGS[i] = PERCENT_FORMAT.format(ZOOM_LEVELS[i]); - ZOOM_SETTINGS[ZOOM_SETTINGS.length - 1] = ZOOM_FIT; + SCALE_LABELS = new String[SCALE_LEVELS.length + 1]; + for (int i = 0; i < SCALE_LEVELS.length; i++) + SCALE_LABELS[i] = PERCENT_FORMAT.format(SCALE_LEVELS[i]); + SCALE_LABELS[SCALE_LABELS.length - 1] = SCALE_FIT; } private final ScaleScrollPane scrollPane; - private JComboBox zoomSelector; + private JComboBox scaleSelector; public ScaleSelector(ScaleScrollPane scroll) { super(new MigLayout()); @@ -44,31 +47,28 @@ public class ScaleSelector extends JPanel { button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - double scale = scrollPane.getScaling(); - scale = getPreviousScale(scale); - scrollPane.setScaling(scale); + final double oldScale = scrollPane.getUserScale(); + final double newScale = getNextLargerScale(oldScale); + scrollPane.setScaling(newScale); + setZoomText(); } }); add(button, "gap"); // Zoom level selector - String[] settings = ZOOM_SETTINGS; - if (!scrollPane.isFittingAllowed()) { - settings = Arrays.copyOf(settings, settings.length - 1); - } - - zoomSelector = new JComboBox(settings); - zoomSelector.setEditable(true); + String[] settings = SCALE_LABELS; + + scaleSelector = new JComboBox<>(settings); + scaleSelector.setEditable(true); setZoomText(); - zoomSelector.addActionListener(new ActionListener() { + scaleSelector.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { - String text = (String) zoomSelector.getSelectedItem(); + String text = (String) scaleSelector.getSelectedItem(); text = text.replaceAll("%", "").trim(); - if (text.toLowerCase(Locale.getDefault()).startsWith(ZOOM_FIT.toLowerCase(Locale.getDefault())) && - scrollPane.isFittingAllowed()) { + if (text.toLowerCase(Locale.getDefault()).startsWith(SCALE_FIT.toLowerCase(Locale.getDefault()))){ scrollPane.setFitting(true); setZoomText(); return; @@ -93,16 +93,17 @@ public class ScaleSelector extends JPanel { setZoomText(); } }); - add(zoomSelector, "gap rel"); + add(scaleSelector, "gap rel"); // Zoom in button button = new JButton(Icons.ZOOM_IN); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - double scale = scrollPane.getScaling(); - scale = getNextScale(scale); + double scale = scrollPane.getUserScale(); + scale = getNextSmallerScale(scale); scrollPane.setScaling(scale); + setZoomText(); } }); add(button, "gapleft rel"); @@ -110,43 +111,42 @@ public class ScaleSelector extends JPanel { } private void setZoomText() { - String text; - double zoom = scrollPane.getScaling(); - text = PERCENT_FORMAT.format(zoom); + final double userScale = scrollPane.getUserScale(); + String text = PERCENT_FORMAT.format(userScale); if (scrollPane.isFitting()) { text = "Fit (" + text + ")"; } - if (!text.equals(zoomSelector.getSelectedItem())) - zoomSelector.setSelectedItem(text); + if (!text.equals(scaleSelector.getSelectedItem())) + scaleSelector.setSelectedItem(text); } - private double getPreviousScale(double scale) { + private static double getNextLargerScale(final double currentScale) { int i; - for (i = 0; i < ZOOM_LEVELS.length - 1; i++) { - if (scale > ZOOM_LEVELS[i] + 0.05 && scale < ZOOM_LEVELS[i + 1] + 0.05) - return ZOOM_LEVELS[i]; + for (i = 0; i < SCALE_LEVELS.length - 1; i++) { + if (currentScale > SCALE_LEVELS[i] + 0.05 && currentScale < SCALE_LEVELS[i + 1] + 0.05) + return SCALE_LEVELS[i]; } - if (scale > ZOOM_LEVELS[ZOOM_LEVELS.length / 2]) { + if (currentScale > SCALE_LEVELS[SCALE_LEVELS.length / 2]) { // scale is large, drop to next lowest full 100% - scale = Math.ceil(scale - 1.05); - return Math.max(scale, ZOOM_LEVELS[i]); + double nextScale = Math.ceil(currentScale - 1.05); + return Math.max(nextScale, SCALE_LEVELS[i]); } // scale is small - return scale / 1.5; + return currentScale / 1.5; } - private double getNextScale(double scale) { + private static double getNextSmallerScale(final double currentScale) { int i; - for (i = 0; i < ZOOM_LEVELS.length - 1; i++) { - if (scale > ZOOM_LEVELS[i] - 0.05 && scale < ZOOM_LEVELS[i + 1] - 0.05) - return ZOOM_LEVELS[i + 1]; + for (i = 0; i < SCALE_LEVELS.length - 1; i++) { + if (currentScale > SCALE_LEVELS[i] - 0.05 && currentScale < SCALE_LEVELS[i + 1] - 0.05) + return SCALE_LEVELS[i + 1]; } - if (scale > ZOOM_LEVELS[ZOOM_LEVELS.length / 2]) { + if (currentScale > SCALE_LEVELS[SCALE_LEVELS.length / 2]) { // scale is large, give next full 100% - scale = Math.floor(scale + 1.05); - return scale; + double nextScale = Math.floor(currentScale + 1.05); + return nextScale; } - return scale * 1.5; + return currentScale * 1.5; } @Override From 904f32d0ba918362fc3fc0474d716d3431eed406 Mon Sep 17 00:00:00 2001 From: JoePfeiffer Date: Thu, 4 Oct 2018 14:36:00 -0600 Subject: [PATCH 2/2] only consider active components in Barrowman equations --- .../aerodynamics/BarrowmanCalculator.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index ad79ec07d..a47262b0e 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -785,24 +785,20 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { private void buildCalcMap(FlightConfiguration configuration) { Iterator iterator; - //System.err.println("> Building Calc Map."); calcMap = new HashMap(); - - iterator = configuration.getRocket().iterator(); - while (iterator.hasNext()) { - RocketComponent c = iterator.next(); - - if (!c.isAerodynamic()) + + for (RocketComponent comp: configuration.getActiveComponents()) { + if (!comp.isAerodynamic()) continue; - RocketComponentCalc calcObj = (RocketComponentCalc) Reflection.construct(BARROWMAN_PACKAGE, c, BARROWMAN_SUFFIX, c); + + RocketComponentCalc calcObj = (RocketComponentCalc) Reflection.construct(BARROWMAN_PACKAGE, comp, BARROWMAN_SUFFIX, comp); //String isNull = (null==calcObj?"null":"valid"); //System.err.println(" >> At component: "+c.getName() +"=="+c.getID()+". CalcObj is "+isNull); - calcMap.put(c, calcObj ); + calcMap.put(comp, calcObj ); } } - @Override public int getModID() { // Only cached data is stored, return constant mod ID