Merge branch 'openrocket:unstable' into fix-tubefins
4
.github/workflows/build.yml
vendored
@ -17,10 +17,10 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Setup JDK 11
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
- name: Ant build
|
||||
run: ant -noinput -buildfile build.xml clean check jar unittest
|
||||
|
4
.idea/ant.xml
generated
@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AntConfiguration">
|
||||
<buildFile url="file://$PROJECT_DIR$/build.xml" />
|
||||
<buildFile url="file://$PROJECT_DIR$/build.xml">
|
||||
<customJdkName value="17" />
|
||||
</buildFile>
|
||||
</component>
|
||||
</project>
|
2
.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
|
19
.idea/runConfigurations/All_tests.xml
generated
@ -1,25 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests" type="JUnit" factoryName="JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="class" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$MODULE_DIR$" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method>
|
||||
<option name="Make" enabled="false" />
|
||||
</method>
|
||||
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
2
.idea/runConfigurations/Openrocket_UI_Jar.xml
generated
@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="Openrocket UI Jar" type="JarApplication">
|
||||
<option name="JAR_PATH" value="$PROJECT_DIR$/build/jar/OpenRocket.jar" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="11" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="17" />
|
||||
<method v="2">
|
||||
<option name="BuildArtifacts" enabled="true">
|
||||
<artifact name="openrocket:jar" />
|
||||
|
90
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AnalysisUIOptions">
|
||||
<option name="ANALYZE_INJECTED_CODE" value="false" />
|
||||
</component>
|
||||
<component name="ArtifactsWorkspaceSettings">
|
||||
<artifacts-to-build>
|
||||
<artifact name="openrocket:jar" />
|
||||
</artifacts-to-build>
|
||||
</component>
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Interface" />
|
||||
<option value="Class" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GitSEFilterConfiguration">
|
||||
<file-type-list>
|
||||
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||
<filtered-out-file-type name="TAG" />
|
||||
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||
</file-type-list>
|
||||
</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/core/resources/l10n/messages.properties" root0="SKIP_INSPECTION" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProblemsViewState">
|
||||
<option name="selectedTabId" value="CurrentFile" />
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="RunManager" selected="Application.SwingStartup">
|
||||
<configuration name="jar" type="AntRunConfiguration">
|
||||
<antsettings antfile="file://$PROJECT_DIR$/build.xml" target="jar">
|
||||
<property name="source" value="17" />
|
||||
<property name="target" value="17" />
|
||||
</antsettings>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="SwingStartup" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="17" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.sf.openrocket.startup.SwingStartup" />
|
||||
<module name="OpenRocket Swing" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="net.sf.openrocket.startup.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Ant Target.jar" />
|
||||
<item itemvalue="Application.SwingStartup" />
|
||||
<item itemvalue="JAR Application.Openrocket UI Jar" />
|
||||
<item itemvalue="JUnit.All tests" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Application.SwingStartup" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="2ecf826f-1227-4870-b9d4-17215cf24a1b" name="Changes" comment="Fix transition config layout" />
|
||||
<created>1625095153807</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1625095153807</updated>
|
||||
</component>
|
||||
</project>
|
@ -13,6 +13,15 @@ Release Notes
|
||||
|
||||
</div>
|
||||
|
||||
<div id="23.xx">
|
||||
|
||||
OpenRocket 23.xx
|
||||
------------------------
|
||||
|
||||
Here, the release notes for the next release will be documented.
|
||||
|
||||
</div>
|
||||
|
||||
<div id="22.02">
|
||||
|
||||
OpenRocket 22.02 (2023-02-08)
|
||||
|
@ -59,7 +59,7 @@
|
||||
<echo level="info">Java/JVM detail version: ${java.version}</echo>
|
||||
<mkdir dir="${classes.dir}"/>
|
||||
<echo level="info">Compiling main classes</echo>
|
||||
<javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" includeantruntime="false" source="1.8" target="1.8"/>
|
||||
<javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" includeantruntime="false" source="17" target="17"/>
|
||||
</target>
|
||||
|
||||
<!-- Executable Eclipse-Jar-In-Jar style JAR -->
|
||||
|
@ -4,6 +4,7 @@ OpenRocket text:
|
||||
Create logo using Gimp's "Blended" logo script.
|
||||
Apply suitable gradient to text layer
|
||||
(softened "Horizon 1" -> "Horizon 1 soft").
|
||||
Font: Kanit (Italic): https://fonts.google.com/specimen/Kanit?query=kanit
|
||||
|
||||
|
||||
Background starfield:
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
# The OpenRocket build version
|
||||
build.version=22.02
|
||||
build.version=23.xx
|
||||
|
||||
# The copyright year for the build. Displayed in the about dialog.
|
||||
# Will show as Copyright 2013-${build.copyright}
|
||||
|
@ -297,6 +297,7 @@ pref.dlg.tab.Launch = Launch
|
||||
pref.dlg.tab.Miscellaneousoptions = Miscellaneous options
|
||||
pref.dlg.lbl.RASAeroWarning = Show warning when saving in RASAero format
|
||||
pref.dlg.lbl.RockSimWarning = Show warning when saving in RockSim format
|
||||
pref.dlg.checkbox.ShowDiscardPreferencesConfirmation = Show confirmation dialog when discarding preferences
|
||||
pref.dlg.but.resetAllPreferences = Reset all preferences
|
||||
pref.dlg.but.resetAllPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.)
|
||||
pref.dlg.but.exportPreferences = Export preferences
|
||||
@ -366,6 +367,10 @@ pref.dlg.lbl.Temperature = Temperature:
|
||||
pref.dlg.lbl.Momentofinertia = Moment of inertia:
|
||||
pref.dlg.lbl.Pressure = Pressure:
|
||||
pref.dlg.lbl.Stability = Stability:
|
||||
pref.dlg.lbl.SecondaryStability = Secondary Stability:
|
||||
pref.dlg.lbl.SecondaryStability.ttip = Select the stability unit that will be displayed alongside the main stability unit in the design view.
|
||||
pref.dlg.checkbox.DisplaySecondaryStability = Display secondary stability unit
|
||||
pref.dlg.checkbox.DisplaySecondaryStability.ttip = If checked, a secondary stability unit will be displayed in the rocket design view.
|
||||
pref.dlg.lbl.FlightTime = Flight time:
|
||||
pref.dlg.lbl.effect1 = The effects will take place the next time you open a window.
|
||||
pref.dlg.lbl.Checkingupdates = Checking for updates\u2026
|
||||
@ -380,6 +385,8 @@ pref.dlg.DescriptionArea.Adddirectories = Add directories, RASP motor files (*.e
|
||||
PreferencesDialog.lbl.language = Interface language:
|
||||
PreferencesDialog.languages.default = System default
|
||||
PreferencesDialog.lbl.languageEffect = The language will change the next time you start OpenRocket.
|
||||
PreferencesDialog.CancelOperation.title = Discard Preference Changes
|
||||
PreferencesDialog.CancelOperation.msg.discardChanges = <html>Are you sure you want to <b>discard the preference changes</b>?</html>
|
||||
|
||||
generalprefs.lbl.language = Interface language
|
||||
generalprefs.languages.default = System default
|
||||
@ -619,7 +626,7 @@ SimuRunDlg.msg.branchErrorOccurred = An error occurred during simulation branch
|
||||
BasicEventSimulationEngine.error.noMotorsDefined = No motors defined in the simulation.
|
||||
BasicEventSimulationEngine.error.activeLengthZero = Active airframe has length 0
|
||||
BasicEventSimulationEngine.error.cantCalculateStability = Can't calculate stability
|
||||
BasicEventSimulationEngine.error.earlyMotorBurnout = Motor burnout without liftoff.
|
||||
BasicEventSimulationEngine.error.earlyMotorBurnout = <html>Motor burnout without liftoff.<br>Use more (powerful) motors, or decrease the rocket mass.</html>
|
||||
BasicEventSimulationEngine.error.noConfiguredIgnition = No motors configured to ignite at liftoff
|
||||
BasicEventSimulationEngine.error.noIgnition = No motors ignited.
|
||||
BasicEventSimulationEngine.error.NaNResult = Simulation resulted in not-a-number (NaN) value, please report a bug.
|
||||
@ -1504,6 +1511,7 @@ TCMotorSelPan.btn.close = Close
|
||||
! PlotDialog
|
||||
PlotDialog.CheckBox.Showdatapoints = Show data points
|
||||
PlotDialog.lbl.Chart = left click drag to zoom area. mouse wheel to zoom. ctrl-mouse wheel to zoom x axis only. ctrl-left click drag to pan. right click drag to zoom dynamically.
|
||||
PlotDialog.lbl.timeSeriesWarning = The data is plotted in time order even though the X axis type is not time.
|
||||
PlotDialog.btn.exportImage = Export Image
|
||||
|
||||
ComponentTree.ttip.massoverride = mass overriden
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 306 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 326 B |
@ -172,8 +172,8 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou
|
||||
|
||||
@Override
|
||||
protected void loadFromPreset(ComponentPreset preset) {
|
||||
this.autoRadius = false;
|
||||
if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
|
||||
this.autoRadius = false;
|
||||
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
|
||||
this.outerRadius = outerDiameter / 2.0;
|
||||
if (preset.has(ComponentPreset.INNER_DIAMETER)) {
|
||||
|
@ -102,7 +102,7 @@ public abstract class ExternalComponent extends RocketComponent {
|
||||
*/
|
||||
@Override
|
||||
public double getComponentMass() {
|
||||
return material.getDensity() * getComponentVolume() * getInstanceCount();
|
||||
return material.getDensity() * getComponentVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -581,7 +581,8 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
* 5. Return twice that since there is a fillet on each side of the fin.
|
||||
*/
|
||||
protected Coordinate calculateFilletVolumeCentroid() {
|
||||
if((null == this.parent) || (!SymmetricComponent.class.isAssignableFrom(this.parent.getClass()))){
|
||||
if ((this.filletRadius == 0) || (this.parent == null) ||
|
||||
(!SymmetricComponent.class.isAssignableFrom(this.parent.getClass()))) {
|
||||
return Coordinate.ZERO;
|
||||
}
|
||||
Coordinate[] mountPoints = this.getRootPoints();
|
||||
@ -594,7 +595,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
final Coordinate finLead = getFinFront();
|
||||
final double xFinEnd = finLead.x + getLength();
|
||||
final Coordinate[] rootPoints = getMountPoints( finLead.x, xFinEnd, -finLead.x, -finLead.y);
|
||||
if (0 == rootPoints.length) {
|
||||
if (rootPoints.length == 0) {
|
||||
return Coordinate.ZERO;
|
||||
}
|
||||
|
||||
@ -616,7 +617,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
|
||||
final Coordinate segmentCentroid = segmentCrossSection.setWeight(segmentVolume);
|
||||
|
||||
filletVolumeCentroid = filletVolumeCentroid.add(segmentCentroid);
|
||||
filletVolumeCentroid = filletVolumeCentroid.average(segmentCentroid);
|
||||
|
||||
prev = cur;
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
|
||||
|
||||
@Override
|
||||
public double getComponentVolume() {
|
||||
return length * Math.PI * (MathUtil.pow2(radius) - MathUtil.pow2(radius - thickness));
|
||||
return length * Math.PI * (MathUtil.pow2(radius) - MathUtil.pow2(radius - thickness)) * getInstanceCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -228,9 +228,12 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
|
||||
|
||||
@Override
|
||||
public Coordinate getComponentCG() {
|
||||
final double parentRadius = parent instanceof SymmetricComponent ?
|
||||
((SymmetricComponent) parent).getRadius(getAxialOffset()) : 0;
|
||||
|
||||
final double CMx = length / 2 + (instanceSeparation * (instanceCount-1)) / 2;
|
||||
final double CMy = Math.cos(this.angleOffsetRad)*getOuterRadius();
|
||||
final double CMz = Math.sin(this.angleOffsetRad)*getOuterRadius();
|
||||
final double CMy = Math.cos(this.angleOffsetRad) * (parentRadius + getOuterRadius());
|
||||
final double CMz = Math.sin(this.angleOffsetRad) * (parentRadius + getOuterRadius());
|
||||
return new Coordinate(CMx, CMy, CMz, getComponentMass());
|
||||
}
|
||||
|
||||
|
@ -304,7 +304,8 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
|
||||
final double volInner = Math.PI*Math.pow( innerDiameter_m/2, 2)*getInnerHeight();
|
||||
final double volStandoff = Math.PI*Math.pow( outerDiameter_m/2, 2)* baseHeight_m;
|
||||
final double volScrew = 2f/3 * Math.PI * MathUtil.pow2(outerDiameter_m/2) * screwHeight_m;
|
||||
return volOuter + volInner + volStandoff + volScrew;
|
||||
final double volInstance = volOuter + volInner + volStandoff + volScrew;
|
||||
return volInstance * getInstanceCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -380,14 +381,16 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
|
||||
final double flangeCM = this.totalHeight_m - getFlangeHeight()/2;
|
||||
final double screwCM = this.totalHeight_m + 4 * this.screwHeight_m / (3 * Math.PI);
|
||||
final double heightCM = (massBase*baseCM + massInner*innerCM + massFlange*flangeCM + massScrew*screwCM)/totalMass;
|
||||
final double parentRadius = parent instanceof SymmetricComponent ?
|
||||
((SymmetricComponent) parent).getRadius(getAxialOffset()) : 0;
|
||||
|
||||
if (heightCM > this.totalHeight_m + this.screwHeight_m) {
|
||||
throw new BugException(" bug found while computing the CG of a RailButton: "+this.getName()+"\n height of CG: "+heightCM);
|
||||
}
|
||||
|
||||
final double CMx = (instanceSeparation * (instanceCount-1)) / 2;
|
||||
final double CMy = Math.cos(this.angleOffsetRad)*heightCM;
|
||||
final double CMz = Math.sin(this.angleOffsetRad)*heightCM;
|
||||
final double CMy = Math.cos(this.angleOffsetRad) * (parentRadius + heightCM);
|
||||
final double CMz = Math.sin(this.angleOffsetRad) * (parentRadius + heightCM);
|
||||
|
||||
return new Coordinate( CMx, CMy, CMz, getComponentMass());
|
||||
}
|
||||
|
@ -60,6 +60,14 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
|
||||
return (mount == ignition);
|
||||
}
|
||||
},
|
||||
//// Launch
|
||||
LAUNCH(trans.get("Stage.SeparationEvent.LAUNCH")) {
|
||||
@Override
|
||||
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
|
||||
return e.getType() == FlightEvent.Type.LAUNCH;
|
||||
}
|
||||
},
|
||||
//// Never
|
||||
NEVER(trans.get("Stage.SeparationEvent.NEVER")) {
|
||||
@Override
|
||||
public boolean isSeparationEvent(FlightEvent e, AxialStage stage) {
|
||||
|
@ -518,6 +518,19 @@ public class TubeFinSet extends Tube implements AxialPositionable, BoxBounded, R
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromPreset(ComponentPreset preset) {
|
||||
super.loadFromPreset(preset);
|
||||
if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
|
||||
this.autoRadius = false;
|
||||
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
|
||||
this.outerRadius = outerDiameter / 2.0;
|
||||
if (preset.has(ComponentPreset.INNER_DIAMETER)) {
|
||||
double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
|
||||
this.thickness = (outerDiameter - innerDiameter) / 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsideColorComponentHandler getInsideColorComponentHandler() {
|
||||
|
184
core/src/net/sf/openrocket/simulation/AbstractEulerStepper.java
Normal file
@ -0,0 +1,184 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceMap;
|
||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.GeodeticComputationStrategy;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.WorldCoordinate;
|
||||
|
||||
public abstract class AbstractEulerStepper extends AbstractSimulationStepper {
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractEulerStepper.class);
|
||||
|
||||
private static final double RECOVERY_TIME_STEP = 0.5;
|
||||
|
||||
protected double cd;
|
||||
|
||||
@Override
|
||||
public SimulationStatus initialize(SimulationStatus status) {
|
||||
this.cd = computeCD(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
private double getCD() {
|
||||
return cd;
|
||||
}
|
||||
|
||||
protected abstract double computeCD(SimulationStatus status);
|
||||
|
||||
@Override
|
||||
public void step(SimulationStatus status, double maxTimeStep) throws SimulationException {
|
||||
|
||||
// Get the atmospheric conditions
|
||||
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
|
||||
|
||||
//// Local wind speed and direction
|
||||
Coordinate windSpeed = modelWindVelocity(status);
|
||||
Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
|
||||
|
||||
// Compute drag force
|
||||
double mach = airSpeed.length() / atmosphere.getMachSpeed();
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = getCD() * dynP * status.getConfiguration().getReferenceArea();
|
||||
|
||||
// n.b. this is constant, and could be calculated once at the beginning of this simulation branch...
|
||||
double rocketMass = calculateStructureMass(status).getMass();
|
||||
double motorMass = calculateMotorMass(status).getMass();
|
||||
|
||||
double mass = rocketMass + motorMass;
|
||||
|
||||
// Compute drag acceleration
|
||||
Coordinate linearAcceleration;
|
||||
if (airSpeed.length() > 0.001) {
|
||||
linearAcceleration = airSpeed.normalize().multiply(-dragForce / mass);
|
||||
} else {
|
||||
linearAcceleration = Coordinate.NUL;
|
||||
}
|
||||
|
||||
// Add effect of gravity
|
||||
double gravity = modelGravity(status);
|
||||
linearAcceleration = linearAcceleration.sub(0, 0, gravity);
|
||||
|
||||
|
||||
// Add coriolis acceleration
|
||||
Coordinate coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration(
|
||||
status.getRocketWorldPosition(), status.getRocketVelocity());
|
||||
linearAcceleration = linearAcceleration.add(coriolisAcceleration);
|
||||
|
||||
// Select tentative time step
|
||||
double timeStep = RECOVERY_TIME_STEP;
|
||||
|
||||
// adjust based on change in acceleration (ie jerk)
|
||||
final double jerk = Math.abs(linearAcceleration.sub(status.getRocketAcceleration()).multiply(1.0/status.getPreviousTimeStep()).length());
|
||||
if (jerk > MathUtil.EPSILON) {
|
||||
timeStep = Math.min(timeStep, 1.0/jerk);
|
||||
}
|
||||
|
||||
// but don't let it get *too* small
|
||||
timeStep = Math.max(timeStep, MIN_TIME_STEP);
|
||||
log.trace("timeStep is " + timeStep);
|
||||
|
||||
// Perform Euler integration
|
||||
Coordinate newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// If I've hit the ground, recalculate time step and position
|
||||
if (newPosition.z < 0) {
|
||||
|
||||
final double a = linearAcceleration.z;
|
||||
final double v = status.getRocketVelocity().z;
|
||||
final double z0 = status.getRocketPosition().z;
|
||||
|
||||
// The new timestep is the solution of
|
||||
// 1/2 at^2 + vt + z0 = 0
|
||||
timeStep = (-v - Math.sqrt(v*v - 2*a*z0))/a;
|
||||
log.trace("ground hit changes timeStep to " + timeStep);
|
||||
|
||||
newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// avoid rounding error in new altitude
|
||||
newPosition = newPosition.setZ(0);
|
||||
}
|
||||
|
||||
status.setSimulationTime(status.getSimulationTime() + timeStep);
|
||||
status.setPreviousTimeStep(timeStep);
|
||||
|
||||
status.setRocketPosition(newPosition);
|
||||
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));
|
||||
status.setRocketAcceleration(linearAcceleration);
|
||||
|
||||
// Update the world coordinate
|
||||
WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
|
||||
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
|
||||
status.setRocketWorldPosition(w);
|
||||
|
||||
// Store data
|
||||
FlightDataBranch data = status.getFlightData();
|
||||
boolean extra = status.getSimulationConditions().isCalculateExtras();
|
||||
data.addPoint();
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
|
||||
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
|
||||
|
||||
airSpeed = status.getRocketVelocity().add(windSpeed);
|
||||
if (extra) {
|
||||
data.setValue(FlightDataType.TYPE_POSITION_XY,
|
||||
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
|
||||
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
|
||||
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
|
||||
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
|
||||
MathUtil.hypot(linearAcceleration.x, linearAcceleration.y));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length());
|
||||
|
||||
double Re = airSpeed.length() *
|
||||
status.getConfiguration().getLengthAerodynamic() /
|
||||
atmosphere.getKinematicViscosity();
|
||||
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_GRAVITY, gravity);
|
||||
|
||||
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
|
||||
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length());
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, airSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_MACH_NUMBER, mach);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_MASS, mass);
|
||||
data.setValue(FlightDataType.TYPE_MOTOR_MASS, motorMass);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0);
|
||||
data.setValue(FlightDataType.TYPE_DRAG_FORCE, dragForce);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, windSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
|
||||
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
|
||||
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
|
||||
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
|
||||
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
|
||||
log.trace("time " + data.getLast(FlightDataType.TYPE_TIME) + ", altitude " + data.getLast(FlightDataType.TYPE_ALTITUDE) + ", velocity " + data.getLast(FlightDataType.TYPE_VELOCITY_Z));
|
||||
}
|
||||
|
||||
}
|
@ -101,7 +101,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
|
||||
SimulationListenerHelper.fireStartSimulation(currentStatus);
|
||||
do {
|
||||
if( null == toSimulate.peek()){
|
||||
if (toSimulate.peek() == null) {
|
||||
break;
|
||||
}
|
||||
currentStatus = toSimulate.pop();
|
||||
@ -707,7 +707,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private FlightData computeCoastTime() {
|
||||
private FlightData computeCoastTime() throws SimulationException {
|
||||
try {
|
||||
SimulationConditions conds = currentStatus.getSimulationConditions().clone();
|
||||
conds.getSimulationListenerList().add(OptimumCoastListener.INSTANCE);
|
||||
@ -715,6 +715,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
|
||||
FlightData d = e.simulate(conds);
|
||||
return d;
|
||||
} catch (SimulationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Exception computing coast time: ", e);
|
||||
return null;
|
||||
|
@ -1,185 +1,18 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceMap;
|
||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.GeodeticComputationStrategy;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.WorldCoordinate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BasicLandingStepper extends AbstractSimulationStepper {
|
||||
private static final Logger log = LoggerFactory.getLogger(BasicLandingStepper.class);
|
||||
|
||||
private static final double RECOVERY_TIME_STEP = 0.5;
|
||||
public class BasicLandingStepper extends AbstractEulerStepper {
|
||||
|
||||
@Override
|
||||
public SimulationStatus initialize(SimulationStatus status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void step(SimulationStatus status, double maxTimeStep) throws SimulationException {
|
||||
double totalCD = 0;
|
||||
double refArea = status.getConfiguration().getReferenceArea();
|
||||
|
||||
// Get the atmospheric conditions
|
||||
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
|
||||
|
||||
//// Local wind speed and direction
|
||||
Coordinate windSpeed = modelWindVelocity(status);
|
||||
Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
|
||||
|
||||
// Get total CD
|
||||
double mach = airSpeed.length() / atmosphere.getMachSpeed();
|
||||
|
||||
protected double computeCD(SimulationStatus status) {
|
||||
// Accumulate CD for all recovery devices
|
||||
cd = 0;
|
||||
final InstanceMap imap = status.getConfiguration().getActiveInstances();
|
||||
for (RecoveryDevice c : status.getDeployedRecoveryDevices()) {
|
||||
totalCD += imap.count(c) * c.getCD(mach) * c.getArea() / refArea;
|
||||
cd += imap.count(c) * c.getCD() * c.getArea() / status.getConfiguration().getReferenceArea();
|
||||
}
|
||||
|
||||
// Compute drag force
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = totalCD * dynP * refArea;
|
||||
|
||||
// Calculate mass data
|
||||
double rocketMass = calculateStructureMass(status).getMass();
|
||||
double motorMass = calculateMotorMass(status).getMass();
|
||||
|
||||
double mass = rocketMass + motorMass;
|
||||
|
||||
// Compute drag acceleration
|
||||
Coordinate linearAcceleration;
|
||||
if (airSpeed.length() > 0.001) {
|
||||
linearAcceleration = airSpeed.normalize().multiply(-dragForce / mass);
|
||||
} else {
|
||||
linearAcceleration = Coordinate.NUL;
|
||||
return cd;
|
||||
}
|
||||
|
||||
// Add effect of gravity
|
||||
double gravity = modelGravity(status);
|
||||
linearAcceleration = linearAcceleration.sub(0, 0, gravity);
|
||||
|
||||
|
||||
// Add coriolis acceleration
|
||||
Coordinate coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration(
|
||||
status.getRocketWorldPosition(), status.getRocketVelocity());
|
||||
linearAcceleration = linearAcceleration.add(coriolisAcceleration);
|
||||
|
||||
|
||||
|
||||
// Select tentative time step
|
||||
double timeStep = RECOVERY_TIME_STEP;
|
||||
|
||||
// adjust based on change in acceleration (ie jerk)
|
||||
final double jerk = Math.abs(linearAcceleration.sub(status.getRocketAcceleration()).multiply(1.0/status.getPreviousTimeStep()).length());
|
||||
if (jerk > MathUtil.EPSILON) {
|
||||
timeStep = Math.min(timeStep, 1.0/jerk);
|
||||
}
|
||||
// but don't let it get *too* small
|
||||
timeStep = Math.max(timeStep, MIN_TIME_STEP);
|
||||
log.trace("timeStep is " + timeStep);
|
||||
|
||||
// Perform Euler integration
|
||||
Coordinate newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// If I've hit the ground, recalculate time step and position
|
||||
if (newPosition.z < 0) {
|
||||
|
||||
final double a = linearAcceleration.z;
|
||||
final double v = status.getRocketVelocity().z;
|
||||
final double z0 = status.getRocketPosition().z;
|
||||
|
||||
// The new timestep is the solution of
|
||||
// 1/2 at^2 + vt + z0 = 0
|
||||
timeStep = (-v - Math.sqrt(v*v - 2*a*z0))/a;
|
||||
log.trace("ground hit changes timeStep to " + timeStep);
|
||||
|
||||
newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// avoid rounding error in new altitude
|
||||
newPosition = newPosition.setZ(0);
|
||||
}
|
||||
|
||||
status.setSimulationTime(status.getSimulationTime() + timeStep);
|
||||
status.setPreviousTimeStep(timeStep);
|
||||
|
||||
status.setRocketPosition(newPosition);
|
||||
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));
|
||||
status.setRocketAcceleration(linearAcceleration);
|
||||
|
||||
// Update the world coordinate
|
||||
WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
|
||||
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
|
||||
status.setRocketWorldPosition(w);
|
||||
|
||||
// Store data
|
||||
FlightDataBranch data = status.getFlightData();
|
||||
boolean extra = status.getSimulationConditions().isCalculateExtras();
|
||||
data.addPoint();
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
|
||||
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
|
||||
|
||||
airSpeed = status.getRocketVelocity().add(windSpeed);
|
||||
if (extra) {
|
||||
data.setValue(FlightDataType.TYPE_POSITION_XY,
|
||||
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
|
||||
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
|
||||
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
|
||||
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
|
||||
MathUtil.hypot(linearAcceleration.x, linearAcceleration.y));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length());
|
||||
|
||||
double Re = airSpeed.length() *
|
||||
status.getConfiguration().getLengthAerodynamic() /
|
||||
atmosphere.getKinematicViscosity();
|
||||
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_GRAVITY, gravity);
|
||||
|
||||
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
|
||||
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length());
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, airSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_MACH_NUMBER, mach);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_MASS, mass);
|
||||
data.setValue(FlightDataType.TYPE_MOTOR_MASS, motorMass);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0);
|
||||
data.setValue(FlightDataType.TYPE_DRAG_FORCE, dragForce);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, windSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
|
||||
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
|
||||
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
|
||||
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
|
||||
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
|
||||
log.trace("time " + data.getLast(FlightDataType.TYPE_TIME) + ", altitude " + data.getLast(FlightDataType.TYPE_ALTITUDE) + ", velocity " + data.getLast(FlightDataType.TYPE_VELOCITY_Z));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceContext;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceMap;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
|
||||
|
||||
public class BasicTumbleStatus extends SimulationStatus {
|
||||
|
||||
// Magic constants from techdoc.pdf
|
||||
private final static double cDFin = 1.42;
|
||||
private final static double cDBt = 0.56;
|
||||
// Fin efficiency. Index is number of fins. The 0th entry is arbitrary and used to
|
||||
// offset the indexes so finEff[1] is the coefficient for one fin from the table in techdoc.pdf
|
||||
private final static double[] finEff = { 0.0, 0.5, 1.0, 1.41, 1.81, 1.73, 1.90, 1.85 };
|
||||
|
||||
private final double drag;
|
||||
|
||||
public BasicTumbleStatus(FlightConfiguration configuration,
|
||||
SimulationConditions simulationConditions) {
|
||||
super(configuration, simulationConditions);
|
||||
this.drag = computeTumbleDrag();
|
||||
}
|
||||
|
||||
public BasicTumbleStatus(SimulationStatus orig) {
|
||||
super(orig);
|
||||
if (orig instanceof BasicTumbleStatus) {
|
||||
this.drag = ((BasicTumbleStatus) orig).drag;
|
||||
} else {
|
||||
this.drag = computeTumbleDrag();
|
||||
}
|
||||
}
|
||||
|
||||
public double getTumbleDrag() {
|
||||
return drag;
|
||||
}
|
||||
|
||||
|
||||
private double computeTumbleDrag() {
|
||||
|
||||
// Computed based on Sampo's experimentation as documented in the pdf.
|
||||
|
||||
// compute the fin and body tube projected areas
|
||||
double aFins = 0.0;
|
||||
double aBt = 0.0;
|
||||
final InstanceMap imap = this.getConfiguration().getActiveInstances();
|
||||
for(Map.Entry<RocketComponent, ArrayList<InstanceContext>> entry: imap.entrySet() ) {
|
||||
final RocketComponent component = entry.getKey();
|
||||
|
||||
if (!component.isAerodynamic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// iterate across component instances
|
||||
final ArrayList<InstanceContext> contextList = entry.getValue();
|
||||
for(InstanceContext context: contextList ) {
|
||||
|
||||
if (component instanceof FinSet) {
|
||||
final FinSet finComponent = ((FinSet) component);
|
||||
final double finArea = finComponent.getPlanformArea();
|
||||
int finCount = finComponent.getFinCount();
|
||||
|
||||
// check bounds on finCount.
|
||||
if (finCount >= finEff.length) {
|
||||
finCount = finEff.length - 1;
|
||||
}
|
||||
|
||||
aFins += finArea * finEff[finCount] / finComponent.getFinCount();
|
||||
|
||||
} else if (component instanceof SymmetricComponent) {
|
||||
aBt += ((SymmetricComponent) component).getComponentPlanformArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (cDFin * aFins + cDBt * aBt);
|
||||
}
|
||||
}
|
@ -1,165 +1,62 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.GeodeticComputationStrategy;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.WorldCoordinate;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceContext;
|
||||
import net.sf.openrocket.rocketcomponent.InstanceMap;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
|
||||
|
||||
public class BasicTumbleStepper extends AbstractSimulationStepper {
|
||||
public class BasicTumbleStepper extends AbstractEulerStepper {
|
||||
|
||||
private static final double RECOVERY_TIME_STEP = 0.5;
|
||||
public double computeCD(SimulationStatus status) {
|
||||
|
||||
@Override
|
||||
public SimulationStatus initialize(SimulationStatus original) {
|
||||
BasicTumbleStatus status = new BasicTumbleStatus(original);
|
||||
status.setWarnings(original.getWarnings());
|
||||
// Computed based on Sampo's experimentation as documented in techdoc.pdf.
|
||||
|
||||
return status;
|
||||
// Magic constants from techdoc.pdf
|
||||
final double cDFin = 1.42;
|
||||
final double cDBt = 0.56;
|
||||
// Fin efficiency. Index is number of fins. The 0th entry is arbitrary and used to
|
||||
// offset the indexes so finEff[1] is the coefficient for one fin from the table in techdoc.pdf
|
||||
final double[] finEff = { 0.0, 0.5, 1.0, 1.41, 1.81, 1.73, 1.90, 1.85 };
|
||||
|
||||
// compute the fin and body tube projected areas
|
||||
double aFins = 0.0;
|
||||
double aBt = 0.0;
|
||||
final InstanceMap imap = status.getConfiguration().getActiveInstances();
|
||||
for(Map.Entry<RocketComponent, ArrayList<InstanceContext>> entry: imap.entrySet() ) {
|
||||
final RocketComponent component = entry.getKey();
|
||||
|
||||
if (!component.isAerodynamic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void step(SimulationStatus status, double maxTimeStep) throws SimulationException {
|
||||
// iterate across component instances
|
||||
final ArrayList<InstanceContext> contextList = entry.getValue();
|
||||
for(InstanceContext context: contextList ) {
|
||||
|
||||
// Get the atmospheric conditions
|
||||
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
|
||||
if (component instanceof FinSet) {
|
||||
final FinSet finComponent = ((FinSet) component);
|
||||
final double finArea = finComponent.getPlanformArea();
|
||||
int finCount = finComponent.getFinCount();
|
||||
|
||||
//// Local wind speed and direction
|
||||
Coordinate windSpeed = modelWindVelocity(status);
|
||||
Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
|
||||
|
||||
// Get total CD
|
||||
double mach = airSpeed.length() / atmosphere.getMachSpeed();
|
||||
|
||||
double tumbleDrag = ((BasicTumbleStatus)status).getTumbleDrag();
|
||||
|
||||
// Compute drag force
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = tumbleDrag * dynP;
|
||||
|
||||
// n.b. this is constant, and could be calculated once at the beginning of this simulation branch...
|
||||
double rocketMass = calculateStructureMass(status).getMass();
|
||||
double motorMass = calculateMotorMass(status).getMass();
|
||||
|
||||
double mass = rocketMass + motorMass;
|
||||
|
||||
// Compute drag acceleration
|
||||
Coordinate linearAcceleration;
|
||||
if (airSpeed.length() > 0.001) {
|
||||
linearAcceleration = airSpeed.normalize().multiply(-dragForce / mass);
|
||||
} else {
|
||||
linearAcceleration = Coordinate.NUL;
|
||||
// check bounds on finCount.
|
||||
if (finCount >= finEff.length) {
|
||||
finCount = finEff.length - 1;
|
||||
}
|
||||
|
||||
// Add effect of gravity
|
||||
double gravity = modelGravity(status);
|
||||
linearAcceleration = linearAcceleration.sub(0, 0, gravity);
|
||||
aFins += finArea * finEff[finCount] / finComponent.getFinCount();
|
||||
|
||||
|
||||
// Add coriolis acceleration
|
||||
Coordinate coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration(
|
||||
status.getRocketWorldPosition(), status.getRocketVelocity());
|
||||
linearAcceleration = linearAcceleration.add(coriolisAcceleration);
|
||||
|
||||
|
||||
|
||||
// Select time step
|
||||
double timeStep = MathUtil.min(0.5 / linearAcceleration.length(), RECOVERY_TIME_STEP);
|
||||
|
||||
// Perform Euler integration
|
||||
Coordinate newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// If I've hit the ground, recalculate time step and position
|
||||
if (newPosition.z < 0) {
|
||||
|
||||
final double a = linearAcceleration.z;
|
||||
final double v = status.getRocketVelocity().z;
|
||||
final double z0 = status.getRocketPosition().z;
|
||||
|
||||
// The new timestep is the solution of
|
||||
// 1/2 at^2 + vt + z0 = 0
|
||||
timeStep = (-v - Math.sqrt(v*v - 2*a*z0))/a;
|
||||
|
||||
newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
|
||||
|
||||
// avoid rounding error in new altitude
|
||||
newPosition = newPosition.setZ(0);
|
||||
} else if (component instanceof SymmetricComponent) {
|
||||
aBt += ((SymmetricComponent) component).getComponentPlanformArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status.setRocketPosition(status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
|
||||
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2)));
|
||||
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));
|
||||
status.setSimulationTime(status.getSimulationTime() + timeStep);
|
||||
|
||||
|
||||
// Update the world coordinate
|
||||
WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
|
||||
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
|
||||
status.setRocketWorldPosition(w);
|
||||
|
||||
|
||||
// Store data
|
||||
FlightDataBranch data = status.getFlightData();
|
||||
boolean extra = status.getSimulationConditions().isCalculateExtras();
|
||||
data.addPoint();
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
|
||||
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
|
||||
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
|
||||
if (extra) {
|
||||
data.setValue(FlightDataType.TYPE_POSITION_XY,
|
||||
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
|
||||
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
|
||||
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
|
||||
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
|
||||
MathUtil.hypot(linearAcceleration.x, linearAcceleration.y));
|
||||
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length());
|
||||
|
||||
double Re = airSpeed.length() *
|
||||
status.getConfiguration().getLengthAerodynamic() /
|
||||
atmosphere.getKinematicViscosity();
|
||||
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
|
||||
data.setValue(FlightDataType.TYPE_GRAVITY, gravity);
|
||||
|
||||
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
|
||||
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length());
|
||||
}
|
||||
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
|
||||
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, airSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_MACH_NUMBER, mach);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_MASS, mass);
|
||||
data.setValue(FlightDataType.TYPE_MOTOR_MASS, motorMass);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0);
|
||||
data.setValue(FlightDataType.TYPE_DRAG_FORCE, dragForce);
|
||||
|
||||
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, windSpeed.length());
|
||||
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
|
||||
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
|
||||
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
|
||||
|
||||
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
|
||||
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
|
||||
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
|
||||
return (cDFin * aFins + cDBt * aBt)/status.getConfiguration().getReferenceArea();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ public abstract class Preferences implements ChangeSource {
|
||||
public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves";
|
||||
|
||||
public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber";
|
||||
|
||||
// Preferences related to units
|
||||
public static final String DISPLAY_SECONDARY_STABILITY = "DisplaySecondaryStability";
|
||||
|
||||
// Preferences related to data export
|
||||
public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
|
||||
public static final String EXPORT_DECIMAL_PLACES = "ExportDecimalPlaces";
|
||||
@ -79,10 +83,11 @@ public abstract class Preferences implements ChangeSource {
|
||||
private static final String OPEN_LEFTMOST_DESIGN_TAB = "OpenLeftmostDesignTab";
|
||||
private static final String SHOW_DISCARD_CONFIRMATION = "IgnoreDiscardEditingWarning";
|
||||
private static final String SHOW_DISCARD_SIMULATION_CONFIRMATION = "IgnoreDiscardSimulationEditingWarning";
|
||||
public static final String MARKER_STYLE_ICON = "MARKER_STYLE_ICON";
|
||||
private static final String SHOW_MARKERS = "SHOW_MARKERS";
|
||||
private static final String SHOW_RASAERO_FORMAT_WARNING = "SHOW_RASAERO_FORMAT_WARNING";
|
||||
private static final String SHOW_ROCKSIM_FORMAT_WARNING = "SHOW_ROCKSIM_FORMAT_WARNING";
|
||||
private static final String SHOW_DISCARD_PREFERENCES_CONFIRMATION = "IgnoreDiscardPreferencesWarning";
|
||||
public static final String MARKER_STYLE_ICON = "MarkerStyleIcon";
|
||||
private static final String SHOW_MARKERS = "ShowMarkers";
|
||||
private static final String SHOW_RASAERO_FORMAT_WARNING = "ShowRASAeroFormatWarning";
|
||||
private static final String SHOW_ROCKSIM_FORMAT_WARNING = "ShowRockSimFormatWarning";
|
||||
private static final String EXPORT_USER_DIRECTORIES = "ExportUserDirectories";
|
||||
private static final String EXPORT_WINDOW_INFORMATION = "ExportWindowInformation";
|
||||
|
||||
@ -201,6 +206,27 @@ public abstract class Preferences implements ChangeSource {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* *********************** Unit Preferences *******************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return whether to display a secondary stability unit in the rocket design view.
|
||||
* @return true if the secondary unit should be displayed, false if not.
|
||||
*/
|
||||
public final boolean isDisplaySecondaryStability() {
|
||||
return this.getBoolean(DISPLAY_SECONDARY_STABILITY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to display a secondary stability unit in the rocket design view.
|
||||
* @param check if true, display the secondary unit, if false not.
|
||||
*/
|
||||
public final void setDisplaySecondaryStability(boolean check) {
|
||||
this.putBoolean(DISPLAY_SECONDARY_STABILITY, check);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ******************************************************************************************
|
||||
*/
|
||||
@ -582,6 +608,22 @@ public abstract class Preferences implements ChangeSource {
|
||||
this.putBoolean(SHOW_DISCARD_SIMULATION_CONFIRMATION, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Answer if a confirmation dialog should be shown when canceling preferences changes.
|
||||
*
|
||||
* @return true if the confirmation dialog should be shown.
|
||||
*/
|
||||
public final boolean isShowDiscardPreferencesConfirmation() {
|
||||
return this.getBoolean(SHOW_DISCARD_PREFERENCES_CONFIRMATION, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable showing a confirmation warning when canceling preferences changes.
|
||||
*/
|
||||
public final void setShowDiscardPreferencesConfirmation(boolean enabled) {
|
||||
this.putBoolean(SHOW_DISCARD_PREFERENCES_CONFIRMATION, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Answer if the always open leftmost tab is enabled.
|
||||
*
|
||||
|
@ -39,6 +39,8 @@ public class UnitGroup {
|
||||
|
||||
public static final UnitGroup UNITS_AREA;
|
||||
public static final UnitGroup UNITS_STABILITY;
|
||||
public static final UnitGroup UNITS_SECONDARY_STABILITY;
|
||||
|
||||
/**
|
||||
* This unit group contains only the caliber unit that never scales the originating "SI" value.
|
||||
* It can be used in cases where the originating value is already in calibers to obtains the correct unit.
|
||||
@ -162,12 +164,10 @@ public class UnitGroup {
|
||||
|
||||
|
||||
UNITS_STABILITY = new UnitGroup();
|
||||
UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm"));
|
||||
UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm"));
|
||||
UNITS_STABILITY.addUnit(new GeneralUnit(1, "m"));
|
||||
UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in"));
|
||||
UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null));
|
||||
UNITS_STABILITY.addUnit(new PercentageOfLengthUnit((Rocket) null));
|
||||
UNITS_SECONDARY_STABILITY = new UnitGroup();
|
||||
addStabilityUnits(UNITS_STABILITY);
|
||||
addStabilityUnits(UNITS_SECONDARY_STABILITY);
|
||||
|
||||
|
||||
UNITS_STABILITY_CALIBERS = new UnitGroup();
|
||||
UNITS_STABILITY_CALIBERS.addUnit(new GeneralUnit(1, "cal"));
|
||||
@ -310,6 +310,7 @@ public class UnitGroup {
|
||||
map.put("ACCELERATION", UNITS_ACCELERATION);
|
||||
map.put("AREA", UNITS_AREA);
|
||||
map.put("STABILITY", UNITS_STABILITY);
|
||||
map.put("SECONDARY_STABILITY", UNITS_SECONDARY_STABILITY);
|
||||
map.put("MASS", UNITS_MASS);
|
||||
map.put("INERTIA", UNITS_INERTIA);
|
||||
map.put("ANGLE", UNITS_ANGLE);
|
||||
@ -366,6 +367,7 @@ public class UnitGroup {
|
||||
UNITS_DISTANCE.setDefaultUnit("m");
|
||||
UNITS_AREA.setDefaultUnit("cm" + SQUARED);
|
||||
UNITS_STABILITY.setDefaultUnit("cal");
|
||||
UNITS_SECONDARY_STABILITY.setDefaultUnit("%");
|
||||
UNITS_VELOCITY.setDefaultUnit("m/s");
|
||||
UNITS_ACCELERATION.setDefaultUnit("m/s" + SQUARED);
|
||||
UNITS_MASS.setDefaultUnit("g");
|
||||
@ -392,6 +394,7 @@ public class UnitGroup {
|
||||
UNITS_DISTANCE.setDefaultUnit("ft");
|
||||
UNITS_AREA.setDefaultUnit("in" + SQUARED);
|
||||
UNITS_STABILITY.setDefaultUnit("cal");
|
||||
UNITS_SECONDARY_STABILITY.setDefaultUnit("%");
|
||||
UNITS_VELOCITY.setDefaultUnit("ft/s");
|
||||
UNITS_ACCELERATION.setDefaultUnit("ft/s" + SQUARED);
|
||||
UNITS_MASS.setDefaultUnit("oz");
|
||||
@ -425,6 +428,7 @@ public class UnitGroup {
|
||||
UNITS_ALL_LENGTHS.setDefaultUnit(2);
|
||||
UNITS_AREA.setDefaultUnit(1);
|
||||
UNITS_STABILITY.setDefaultUnit(4);
|
||||
UNITS_SECONDARY_STABILITY.setDefaultUnit(5);
|
||||
UNITS_STABILITY_CALIBERS.setDefaultUnit(0);
|
||||
UNITS_VELOCITY.setDefaultUnit(0);
|
||||
UNITS_WINDSPEED.setDefaultUnit(0);
|
||||
@ -449,6 +453,15 @@ public class UnitGroup {
|
||||
UNITS_FREQUENCY.setDefaultUnit(1);
|
||||
}
|
||||
|
||||
private static void addStabilityUnits(UnitGroup stabilityUnit) {
|
||||
stabilityUnit.addUnit(new GeneralUnit(0.001, "mm"));
|
||||
stabilityUnit.addUnit(new GeneralUnit(0.01, "cm"));
|
||||
stabilityUnit.addUnit(new GeneralUnit(1, "m"));
|
||||
stabilityUnit.addUnit(new GeneralUnit(0.0254, "in"));
|
||||
stabilityUnit.addUnit(new CaliberUnit((Rocket) null));
|
||||
stabilityUnit.addUnit(new PercentageOfLengthUnit((Rocket) null));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a UnitGroup for stability units based on the rocket.
|
||||
@ -456,8 +469,18 @@ public class UnitGroup {
|
||||
* @param rocket the rocket from which to calculate the caliber
|
||||
* @return the unit group
|
||||
*/
|
||||
public static UnitGroup stabilityUnits(Rocket rocket) {
|
||||
return new StabilityUnitGroup(rocket);
|
||||
public static StabilityUnitGroup stabilityUnits(Rocket rocket) {
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_STABILITY, rocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a UnitGroup for secondary stability units based on the rocket.
|
||||
*
|
||||
* @param rocket the rocket from which to calculate the caliber
|
||||
* @return the unit group
|
||||
*/
|
||||
public static StabilityUnitGroup secondaryStabilityUnits(Rocket rocket) {
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_SECONDARY_STABILITY, rocket);
|
||||
}
|
||||
|
||||
|
||||
@ -467,8 +490,18 @@ public class UnitGroup {
|
||||
* @param config the rocket configuration from which to calculate the caliber
|
||||
* @return the unit group
|
||||
*/
|
||||
public static UnitGroup stabilityUnits(FlightConfiguration config) {
|
||||
return new StabilityUnitGroup(config);
|
||||
public static StabilityUnitGroup stabilityUnits(FlightConfiguration config) {
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_STABILITY, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a UnitGroup for stability units based on the rocket configuration.
|
||||
*
|
||||
* @param config the rocket configuration from which to calculate the caliber
|
||||
* @return the unit group
|
||||
*/
|
||||
public static StabilityUnitGroup secondaryStabilityUnits(FlightConfiguration config) {
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_SECONDARY_STABILITY, config);
|
||||
}
|
||||
|
||||
|
||||
@ -479,7 +512,17 @@ public class UnitGroup {
|
||||
* @return the unit group
|
||||
*/
|
||||
public static UnitGroup stabilityUnits(double reference) {
|
||||
return new StabilityUnitGroup(reference);
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_STABILITY, reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a UnitGroup for secondary stability units based on a constant caliber.
|
||||
*
|
||||
* @param reference the constant reference length
|
||||
* @return the unit group
|
||||
*/
|
||||
public static UnitGroup secondaryStabilityUnits(double reference) {
|
||||
return new StabilityUnitGroup(UnitGroup.UNITS_SECONDARY_STABILITY, reference);
|
||||
}
|
||||
|
||||
|
||||
@ -717,19 +760,27 @@ public class UnitGroup {
|
||||
* A private class that switches the CaliberUnit to a rocket-specific CaliberUnit.
|
||||
* All other methods are passed through to UNITS_STABILITY.
|
||||
*/
|
||||
private static class StabilityUnitGroup extends UnitGroup {
|
||||
public static class StabilityUnitGroup extends UnitGroup {
|
||||
private final PercentageOfLengthUnit percentageOfLengthUnit;
|
||||
private final UnitGroup stabilityUnit;
|
||||
|
||||
public StabilityUnitGroup(double ref) { this(new CaliberUnit(ref), new PercentageOfLengthUnit(ref)); }
|
||||
|
||||
public StabilityUnitGroup(Rocket rocket) {
|
||||
this(new CaliberUnit(rocket), new PercentageOfLengthUnit(rocket));
|
||||
public StabilityUnitGroup(UnitGroup stabilityUnit, double ref) {
|
||||
this(stabilityUnit, new CaliberUnit(ref), new PercentageOfLengthUnit(ref));
|
||||
}
|
||||
|
||||
public StabilityUnitGroup(FlightConfiguration config) { this(new CaliberUnit(config), new PercentageOfLengthUnit(config)); }
|
||||
public StabilityUnitGroup(UnitGroup stabilityUnit, Rocket rocket) {
|
||||
this(stabilityUnit, new CaliberUnit(rocket), new PercentageOfLengthUnit(rocket));
|
||||
}
|
||||
|
||||
private StabilityUnitGroup(CaliberUnit caliberUnit, PercentageOfLengthUnit percentageOfLengthUnit) {
|
||||
this.units.addAll(UnitGroup.UNITS_STABILITY.units);
|
||||
this.defaultUnit = UnitGroup.UNITS_STABILITY.defaultUnit;
|
||||
public StabilityUnitGroup(UnitGroup stabilityUnit, FlightConfiguration config) {
|
||||
this(stabilityUnit, new CaliberUnit(config), new PercentageOfLengthUnit(config));
|
||||
}
|
||||
|
||||
private StabilityUnitGroup(UnitGroup stabilityUnit, CaliberUnit caliberUnit, PercentageOfLengthUnit percentageOfLengthUnit) {
|
||||
this.percentageOfLengthUnit = percentageOfLengthUnit;
|
||||
this.stabilityUnit = stabilityUnit;
|
||||
this.units.addAll(stabilityUnit.units);
|
||||
this.defaultUnit = stabilityUnit.defaultUnit;
|
||||
for (int i = 0; i < units.size(); i++) {
|
||||
if (units.get(i) instanceof CaliberUnit) {
|
||||
units.set(i, caliberUnit);
|
||||
@ -744,7 +795,15 @@ public class UnitGroup {
|
||||
@Override
|
||||
public void setDefaultUnit(int n) {
|
||||
super.setDefaultUnit(n);
|
||||
UNITS_STABILITY.setDefaultUnit(n);
|
||||
this.stabilityUnit.setDefaultUnit(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage of length unit. (Stability in %)
|
||||
* @return the percentage of length unit.
|
||||
*/
|
||||
public Unit getPercentageOfLengthUnit() {
|
||||
return this.percentageOfLengthUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public class MassCalculatorTest extends BaseTestCase {
|
||||
assertEquals(" Alpha III Empty Mass is incorrect: ", expRocketDryMass, actualRocketDryMass, EPSILON);
|
||||
|
||||
double expCMx = 0.1917685523;
|
||||
double expCMy = -0.00006340812673; // Slight offset due to launch lug
|
||||
double expCMy = -0.000317040634; // Slight offset due to launch lug
|
||||
Coordinate expCM = new Coordinate(expCMx, expCMy, 0, expRocketDryMass);
|
||||
assertEquals("Simple Rocket CM.x is incorrect: ", expCM.x, actualRocketDryCM.x, EPSILON);
|
||||
assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, actualRocketDryCM.y, EPSILON);
|
||||
@ -73,7 +73,7 @@ public class MassCalculatorTest extends BaseTestCase {
|
||||
assertEquals("Simple Rocket CM is incorrect: ", expCM, actualRocketDryCM);
|
||||
|
||||
|
||||
double expMOIrot = 1.8763734635622462E-5;
|
||||
double expMOIrot = 1.888136072268211E-5;
|
||||
double expMOIlong = 1.7808603404853048E-4;
|
||||
|
||||
double actualMOIrot = actualStructure.getRotationalInertia();
|
||||
@ -118,7 +118,7 @@ public class MassCalculatorTest extends BaseTestCase {
|
||||
assertEquals(" Alpha III Total Mass (with motor: " + desig + ") is incorrect: ", expRocketLaunchMass, actualRocketLaunchMass, EPSILON);
|
||||
|
||||
double expCMx = 0.20996455968266833;
|
||||
double expCMy = -0.00003845163503; // Slight offset due to launch lug
|
||||
double expCMy = -0.00019225817513303; // Slight offset due to launch lug
|
||||
Coordinate expCM = new Coordinate(expCMx, expCMy, 0, expRocketLaunchMass);
|
||||
assertEquals("Simple Rocket CM.x is incorrect: ", expCM.x, actualRocketLaunchCM.x, EPSILON);
|
||||
assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, actualRocketLaunchCM.y, EPSILON);
|
||||
@ -1270,4 +1270,36 @@ public class MassCalculatorTest extends BaseTestCase {
|
||||
assertEquals(0.02, bodyTube.getSectionMass(), EPSILON);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTubeFinMass() {
|
||||
Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket();
|
||||
AxialStage stage = rocket.getStage(0);
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
stage.addChild(bodyTube);
|
||||
TubeFinSet tubeFinSet = new TubeFinSet();
|
||||
tubeFinSet.setOuterRadius(0.04);
|
||||
tubeFinSet.setThickness(0.002);
|
||||
tubeFinSet.setLength(0.1);
|
||||
tubeFinSet.setInstanceCount(3);
|
||||
bodyTube.addChild(tubeFinSet);
|
||||
|
||||
assertEquals(0.0001470265, tubeFinSet.getComponentVolume(), EPSILON);
|
||||
assertEquals(0.0999780446, tubeFinSet.getComponentMass(), EPSILON);
|
||||
assertEquals(0.0999780446, tubeFinSet.getMass(), EPSILON);
|
||||
|
||||
tubeFinSet.setInstanceCount(4);
|
||||
|
||||
assertEquals(0.000196035, tubeFinSet.getComponentVolume(), EPSILON);
|
||||
assertEquals(0.133304059, tubeFinSet.getComponentMass(), EPSILON);
|
||||
assertEquals(0.133304059, tubeFinSet.getMass(), EPSILON);
|
||||
|
||||
tubeFinSet.setMassOverridden(true);
|
||||
tubeFinSet.setOverrideMass(0.02);
|
||||
|
||||
assertEquals(0.133304059, tubeFinSet.getComponentMass(), EPSILON);
|
||||
assertEquals(0.02, tubeFinSet.getMass(), EPSILON);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
@Test
|
||||
public void testCMSingleInstance() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
LaunchLug lug = new LaunchLug();
|
||||
lug.setLength(0.1);
|
||||
lug.setOuterRadius(0.02);
|
||||
@ -75,7 +76,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.045, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
@ -84,14 +85,14 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.045, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0225, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.03897114, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -102,7 +103,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.04, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
|
||||
|
||||
@ -111,20 +112,21 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.04, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.034641016, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMSingleInstanceOverride() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
LaunchLug lug = new LaunchLug();
|
||||
lug.setLength(0.1);
|
||||
lug.setOuterRadius(0.02);
|
||||
@ -135,7 +137,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.045, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
@ -144,14 +146,14 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.045, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0225, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.03897114, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -165,7 +167,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.04, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
|
||||
@ -174,20 +176,21 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.04, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.034641016, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMMultipleInstances() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
LaunchLug lug = new LaunchLug();
|
||||
lug.setLength(0.1);
|
||||
lug.setOuterRadius(0.02);
|
||||
@ -198,7 +201,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.045, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
@ -207,14 +210,14 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.045, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0225, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.03897114, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -227,7 +230,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.04, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
|
||||
|
||||
@ -236,20 +239,21 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.04, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.034641016, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMMultipleInstancesOverride() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
LaunchLug lug = new LaunchLug();
|
||||
lug.setLength(0.1);
|
||||
lug.setOuterRadius(0.02);
|
||||
@ -262,7 +266,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", -0.045, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
@ -271,14 +275,14 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.045, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0225, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.03897114, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -294,7 +298,7 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.04, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
|
||||
@ -303,14 +307,14 @@ public class LaunchLugTest extends BaseTestCase {
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", 0.04, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
|
||||
lug.setAngleOffset(-Math.PI / 3);
|
||||
CG = lug.getCG();
|
||||
assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong y value: ", 0.02, CG.y, EPSILON);
|
||||
assertEquals(" LaunchLug CG has the wrong z value: ", -0.034641016, CG.z, EPSILON);
|
||||
assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
@Test
|
||||
public void testCMSingleInstance() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
RailButton button = new RailButton();
|
||||
button.setOuterDiameter(0.05);
|
||||
button.setTotalHeight(0.05);
|
||||
@ -21,7 +22,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.05, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
@ -30,14 +31,14 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.05, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.04330127, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -48,7 +49,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.035, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
|
||||
|
||||
@ -57,20 +58,21 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.035, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0175, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.03031089, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMSingleInstanceOverride() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
RailButton button = new RailButton();
|
||||
button.setOuterDiameter(0.05);
|
||||
button.setTotalHeight(0.05);
|
||||
@ -81,7 +83,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.05, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
@ -90,14 +92,14 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.05, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.04330127, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -111,7 +113,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.035, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
|
||||
@ -120,20 +122,21 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.035, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0175, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.03031089, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMMultipleInstances() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
RailButton button = new RailButton();
|
||||
button.setOuterDiameter(0.05);
|
||||
button.setTotalHeight(0.05);
|
||||
@ -144,7 +147,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.05, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
@ -153,14 +156,14 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.05, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.04330127, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -173,7 +176,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.035, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
|
||||
|
||||
@ -182,20 +185,21 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.035, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0175, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.03031089, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMMultipleInstancesOverride() {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.025);
|
||||
RailButton button = new RailButton();
|
||||
button.setOuterDiameter(0.05);
|
||||
button.setTotalHeight(0.05);
|
||||
@ -208,7 +212,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
// Test normal CG
|
||||
Coordinate CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", -0.05, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
@ -217,14 +221,14 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.05, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.025, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.04330127, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
|
||||
|
||||
|
||||
@ -240,7 +244,7 @@ public class RailButtonTest extends BaseTestCase {
|
||||
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.035, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
|
||||
@ -249,14 +253,14 @@ public class RailButtonTest extends BaseTestCase {
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", 0.035, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
|
||||
button.setAngleOffset(-Math.PI / 3);
|
||||
CG = button.getCG();
|
||||
assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong y value: ", 0.0175, CG.y, EPSILON);
|
||||
assertEquals(" RailButton CG has the wrong z value: ", -0.03031089, CG.z, EPSILON);
|
||||
assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,32 @@ public class TrapezoidFinSetTest extends BaseTestCase {
|
||||
return rkt;
|
||||
}
|
||||
|
||||
private Rocket createFreeformFinOnTransition() {
|
||||
final Rocket rkt = new Rocket();
|
||||
final AxialStage stg = new AxialStage();
|
||||
rkt.addChild(stg);
|
||||
Transition transition = new Transition();
|
||||
transition.setLength(0.2);
|
||||
transition.setForeRadius(0.1);
|
||||
transition.setAftRadius(0.3);
|
||||
transition.setShapeType(Transition.Shape.OGIVE);
|
||||
stg.addChild(transition);
|
||||
FreeformFinSet fins = new FreeformFinSet();
|
||||
fins.setFinCount(1);
|
||||
fins.setAxialOffset(AxialMethod.MIDDLE, 0.0);
|
||||
fins.setMaterial(Material.newMaterial(Material.Type.BULK, "Fin-Test-Material", 1.0, true));
|
||||
fins.setThickness(0.005); // == 5 mm
|
||||
|
||||
transition.addChild(fins);
|
||||
|
||||
fins.setTabLength(0.00);
|
||||
|
||||
fins.setFilletRadius(0.0);
|
||||
|
||||
rkt.enableEvents();
|
||||
return rkt;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplicity() {
|
||||
final TrapezoidFinSet trapFins = new TrapezoidFinSet();
|
||||
@ -187,6 +213,33 @@ public class TrapezoidFinSetTest extends BaseTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilletCalculationsOnTransition() {
|
||||
final Rocket rkt = createFreeformFinOnTransition();
|
||||
Transition transition = (Transition) rkt.getChild(0).getChild(0);
|
||||
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
|
||||
|
||||
fins.setFilletRadius(0.005);
|
||||
fins.setFilletMaterial(Material.newMaterial(Material.Type.BULK, "Fillet-Test-Material", 1.0, true));
|
||||
|
||||
// used for fillet and edge calculations:
|
||||
//
|
||||
// [1] +--+ [2]
|
||||
// / \
|
||||
// / \
|
||||
// [0] +--------+ [3]
|
||||
//
|
||||
assertEquals(0.05, fins.getLength(), EPSILON);
|
||||
assertEquals("Transition fore radius doesn't match: ", 0.1, transition.getForeRadius(), EPSILON);
|
||||
assertEquals("Transition aft radius doesn't match: ", 0.3, transition.getAftRadius(), EPSILON);
|
||||
|
||||
final Coordinate actVolume = fins.calculateFilletVolumeCentroid();
|
||||
|
||||
assertEquals("Fin volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON);
|
||||
assertEquals("Fin mass center.x doesn't match: ", 0.024393025, actVolume.x, EPSILON);
|
||||
assertEquals("Fin mass center.y doesn't match: ", 0.190479957, actVolume.y, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrapezoidCGComputation() {
|
||||
{
|
||||
|
@ -20,4 +20,3 @@ media/
|
||||
.DS_Store
|
||||
|
||||
code_signing/
|
||||
openrocket-22.xx.install4j~
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<install4j version="10.0.4" transformSequenceNumber="10">
|
||||
<directoryPresets config="../../core/resources/pix/icon" />
|
||||
<application name="OpenRocket" applicationId="8434-9327-1469-6373" mediaDir="media" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.version}_${compiler:sys.platform}" shortName="OpenRocket" publisher="OpenRocket" publisherWeb="http://openrocket.info" version="22.02" allPathsRelative="true" convertDotsToUnderscores="false" macVolumeId="5f58a2be20d8e22f" javaMinVersion="11" javaMaxVersion="11" jdkMode="jdk" jdkName="JDK 11.0">
|
||||
<application name="OpenRocket" applicationId="8434-9327-1469-6373" mediaDir="media" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.version}_${compiler:sys.platform}" shortName="OpenRocket" publisher="OpenRocket" publisherWeb="http://openrocket.info" version="23.xx" allPathsRelative="true" convertDotsToUnderscores="false" macVolumeId="5f58a2be20d8e22f" javaMinVersion="17" javaMaxVersion="17" jdkMode="jdk" jdkName="JDK 11.0">
|
||||
<codeSigning macEnabled="true" macPkcs12File="./code_signing/OpenRocket_macOS.p12" windowsEnabled="true" windowsPkcs12File="./code_signing/OpenRocket_Windows.pfx" macNotarize="true" appleId="sibo.vangool@hotmail.com" />
|
||||
<jreBundles jdkProviderId="Liberica" release="11/11.0.17+7">
|
||||
<jreBundles jdkProviderId="Liberica" release="17/17.0.7+7">
|
||||
<modules>
|
||||
<defaultModules set="jre" />
|
||||
<module name="java.scripting" />
|
||||
@ -19,7 +19,7 @@
|
||||
</entries>
|
||||
</files>
|
||||
<launchers>
|
||||
<launcher name="OpenRocket 22.02" id="59" icnsFile="../../core/resources/pix/icon/icon-macos.icns">
|
||||
<launcher name="OpenRocket 23.xx" id="59" icnsFile="../../core/resources/pix/icon/icon-macos.icns">
|
||||
<executable name="OpenRocket" iconSet="true" iconFile="../../core/resources/pix/icon/icon-windows.ico" executableDir="." executableMode="gui">
|
||||
<versionInfo include="true" fileDescription="A model rocket flight-trajectory simulator." legalCopyright="Copyright 2007-2023 Sampo Niskanen and Others" internalName="${compiler:sys.shortName} ${compiler:sys.version}" />
|
||||
</executable>
|
@ -18,6 +18,9 @@ import java.io.FileOutputStream;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.event.HyperlinkListener;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import javax.swing.JEditorPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
@ -188,6 +191,7 @@ public class DescriptionArea extends JScrollPane {
|
||||
}
|
||||
|
||||
});
|
||||
setForeground(editorPane.getForeground());
|
||||
editorPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
@ -203,4 +207,22 @@ public class DescriptionArea extends JScrollPane {
|
||||
}
|
||||
}
|
||||
|
||||
public void setBackground(Color color) {
|
||||
if (editorPane == null) return;
|
||||
editorPane.setBackground(color);
|
||||
StyledDocument styledDocument = (StyledDocument) editorPane.getDocument();
|
||||
SimpleAttributeSet attributeSet = new SimpleAttributeSet();
|
||||
StyleConstants.setForeground(attributeSet, color);
|
||||
styledDocument.setCharacterAttributes(0, styledDocument.getLength(), attributeSet, false);
|
||||
}
|
||||
|
||||
public void setForeground(Color color) {
|
||||
if (editorPane == null) return;
|
||||
editorPane.setForeground(color);
|
||||
StyledDocument styledDocument = (StyledDocument) editorPane.getDocument();
|
||||
SimpleAttributeSet attributeSet = new SimpleAttributeSet();
|
||||
StyleConstants.setForeground(attributeSet, color);
|
||||
styledDocument.setCharacterAttributes(0, styledDocument.getLength(), attributeSet, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package net.sf.openrocket.gui.dialogs.preferences;
|
||||
import java.awt.Dialog.ModalityType;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -40,7 +42,6 @@ import net.sf.openrocket.gui.util.PreferencesExporter;
|
||||
import net.sf.openrocket.gui.util.PreferencesImporter;
|
||||
import net.sf.openrocket.l10n.L10N;
|
||||
import net.sf.openrocket.logging.Markers;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.startup.Preferences;
|
||||
import net.sf.openrocket.util.BuildProperties;
|
||||
import net.sf.openrocket.util.Named;
|
||||
@ -255,6 +256,17 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
|
||||
});
|
||||
this.add(rocksimWarningDialogBox,"spanx, wrap");
|
||||
|
||||
//// Show confirmation dialog when discarding preferences
|
||||
final JCheckBox prefsDiscardBox = new JCheckBox(trans.get("pref.dlg.checkbox.ShowDiscardPreferencesConfirmation"));
|
||||
prefsDiscardBox.setSelected(preferences.isShowDiscardPreferencesConfirmation());
|
||||
prefsDiscardBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
preferences.setShowDiscardPreferencesConfirmation(e.getStateChange() == ItemEvent.SELECTED);
|
||||
}
|
||||
});
|
||||
this.add(prefsDiscardBox,"spanx, wrap");
|
||||
|
||||
// Preference buttons
|
||||
JPanel buttonPanel = new JPanel(new MigLayout("fillx, ins 0"));
|
||||
|
||||
@ -315,7 +327,7 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
|
||||
}
|
||||
}
|
||||
});
|
||||
buttonPanel.add(resetAllPreferences, "pushx, right, wrap");
|
||||
buttonPanel.add(resetAllPreferences, "pushx, right, gaptop 20lp, wrap");
|
||||
|
||||
this.add(buttonPanel, "spanx, growx, pushy, bottom, wrap");
|
||||
}
|
||||
|
@ -1,21 +1,31 @@
|
||||
package net.sf.openrocket.gui.dialogs.preferences;
|
||||
|
||||
import java.awt.Dialog;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.main.BasicFrame;
|
||||
import net.sf.openrocket.gui.util.GUIUtil;
|
||||
import net.sf.openrocket.gui.util.PreferencesExporter;
|
||||
import net.sf.openrocket.gui.util.PreferencesImporter;
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
@ -35,6 +45,9 @@ public class PreferencesDialog extends JDialog {
|
||||
|
||||
private BasicFrame parentFrame;
|
||||
|
||||
private boolean storePreferences = true;
|
||||
private File initPrefsFile = null;
|
||||
|
||||
private PreferencesDialog(BasicFrame parent) {
|
||||
// // Preferences
|
||||
super(parent, trans.get("pref.dlg.title.Preferences"),
|
||||
@ -42,6 +55,9 @@ public class PreferencesDialog extends JDialog {
|
||||
|
||||
this.parentFrame = parent;
|
||||
|
||||
// First store the initial preferences
|
||||
initPrefsFile = storeInitPreferences();
|
||||
|
||||
JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]",
|
||||
"[grow][]"));
|
||||
|
||||
@ -78,16 +94,42 @@ public class PreferencesDialog extends JDialog {
|
||||
// tabbedPane.addTab(trans.get("pref.dlg.tab.Colors"),
|
||||
// new DisplayPreferencesPanel());
|
||||
|
||||
// Close button
|
||||
JButton close = new SelectColorButton(trans.get("dlg.but.close"));
|
||||
close.addActionListener(new ActionListener() {
|
||||
|
||||
//// Cancel button
|
||||
JButton cancelButton = new SelectColorButton(trans.get("dlg.but.cancel"));
|
||||
cancelButton.setToolTipText(trans.get("SimulationEditDialog.btn.Cancel.ttip"));
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
PreferencesDialog.this.setVisible(false);
|
||||
PreferencesDialog.this.dispose();
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Apply the cancel operation if set to auto discard in preferences
|
||||
if (!preferences.isShowDiscardPreferencesConfirmation()) {
|
||||
closeDialog(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Yes/No dialog: Are you sure you want to discard your changes?
|
||||
JPanel msg = createCancelOperationContent();
|
||||
int resultYesNo = JOptionPane.showConfirmDialog(PreferencesDialog.this, msg,
|
||||
trans.get("PreferencesDialog.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
if (resultYesNo == JOptionPane.YES_OPTION) {
|
||||
closeDialog(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
panel.add(close, "span, right, tag close");
|
||||
panel.add(cancelButton, "span, split 2, right, tag cancel");
|
||||
|
||||
//// Ok button
|
||||
JButton okButton = new SelectColorButton(trans.get("dlg.but.ok"));
|
||||
okButton.setToolTipText(trans.get("SimulationEditDialog.btn.OK.ttip"));
|
||||
okButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
closeDialog(true);
|
||||
}
|
||||
});
|
||||
panel.add(okButton, "tag ok");
|
||||
|
||||
|
||||
|
||||
this.setContentPane(panel);
|
||||
pack();
|
||||
@ -96,7 +138,22 @@ public class PreferencesDialog extends JDialog {
|
||||
this.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
preferences.storeDefaultUnits();
|
||||
// We don't want to lose the preference for the confirmation dialog
|
||||
boolean isShowDiscardConfirmation = preferences.isShowDiscardPreferencesConfirmation();
|
||||
|
||||
// Reload initial preferences
|
||||
if (!storePreferences) {
|
||||
loadInitPreferences();
|
||||
}
|
||||
|
||||
// Store the preference for showing the confirmation dialog
|
||||
preferences.setShowDiscardPreferencesConfirmation(isShowDiscardConfirmation);
|
||||
|
||||
// Delete the init prefs
|
||||
if (initPrefsFile != null) {
|
||||
initPrefsFile.delete();
|
||||
}
|
||||
|
||||
// Make sure unit change applies to the rocket figure
|
||||
if (parent != null) {
|
||||
parent.getRocketPanel().updateExtras();
|
||||
@ -106,13 +163,72 @@ public class PreferencesDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
GUIUtil.setDisposableDialogOptions(this, close);
|
||||
GUIUtil.setDisposableDialogOptions(this, okButton);
|
||||
}
|
||||
|
||||
public BasicFrame getParentFrame() {
|
||||
return parentFrame;
|
||||
}
|
||||
|
||||
private void closeDialog(boolean storeChanges) {
|
||||
storePreferences = storeChanges;
|
||||
PreferencesDialog.this.setVisible(false);
|
||||
PreferencesDialog.this.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the intial preferences in a temporary file, and return that file.
|
||||
* @return the file containing the initial preferences, or null if something went wrong
|
||||
*/
|
||||
private File storeInitPreferences() {
|
||||
try {
|
||||
File outputFile = Files.createTempFile("ORInitPrefs_" + System.currentTimeMillis(), ".xml").toFile();
|
||||
try (FileOutputStream outputFos = new FileOutputStream(outputFile)) {
|
||||
PreferencesExporter.exportPreferencesToFile(preferences.getPreferences(), outputFos, false);
|
||||
log.debug("Initial preferences stored in temporary file: " + outputFile.getAbsolutePath());
|
||||
} catch (BackingStoreException e) {
|
||||
log.error("Could not store initial preferences", e);
|
||||
return null;
|
||||
}
|
||||
return outputFile;
|
||||
} catch (IOException e) {
|
||||
log.error("Could not create temporary preferences file", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the initial stored preferences back (restores preferences).
|
||||
*/
|
||||
private void loadInitPreferences() {
|
||||
if (initPrefsFile == null) {
|
||||
return;
|
||||
}
|
||||
PreferencesImporter.importPreferences(initPrefsFile);
|
||||
}
|
||||
|
||||
private JPanel createCancelOperationContent() {
|
||||
JPanel panel = new JPanel(new MigLayout());
|
||||
String msg = trans.get("PreferencesDialog.CancelOperation.msg.discardChanges");
|
||||
JLabel msgLabel = new JLabel(msg);
|
||||
JCheckBox dontAskAgain = new JCheckBox(trans.get("SimulationEditDialog.CancelOperation.checkbox.dontAskAgain"));
|
||||
dontAskAgain.setSelected(false);
|
||||
dontAskAgain.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
preferences.setShowDiscardPreferencesConfirmation(false);
|
||||
}
|
||||
// Unselected state should be not be possible and thus not be handled
|
||||
}
|
||||
});
|
||||
|
||||
panel.add(msgLabel, "left, wrap");
|
||||
panel.add(dontAskAgain, "left, gaptop para");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
// ////// Singleton implementation ////////
|
||||
|
||||
private static PreferencesDialog dialog = null;
|
||||
|
@ -3,11 +3,15 @@ package net.sf.openrocket.gui.dialogs.preferences;
|
||||
import java.awt.LayoutManager;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
@ -18,122 +22,146 @@ import net.sf.openrocket.gui.widgets.SelectColorButton;
|
||||
public class UnitsPreferencesPanel extends PreferencesPanel {
|
||||
|
||||
public UnitsPreferencesPanel(JDialog parent) {
|
||||
super(parent, new MigLayout("", "[][]40lp[][]"));
|
||||
super(parent, new MigLayout("", "[]40lp[]"));
|
||||
JComboBox<?> combo;
|
||||
JPanel leftPanel = new JPanel(new MigLayout("ins 0"));
|
||||
JPanel rightPanel = new JPanel(new MigLayout("ins 0"));
|
||||
|
||||
//// Select your preferred units:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Selectprefunits")), "span, wrap paragraph");
|
||||
|
||||
|
||||
// -------------- LEFT PANEL
|
||||
|
||||
//// Rocket dimensions:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Rocketdimensions")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Rocketdimensions")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Line density:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Linedensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Motor dimensions:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Motordimensions")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Motordimensions")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Surface density:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Surfacedensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Distance:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Distance")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Distance")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Bulk density::
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Bulkdensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Velocity:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Velocity")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Velocity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Surface roughness:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Surfaceroughness")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Acceleration:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Acceleration")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Acceleration")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Area:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Area")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Mass:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Mass")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Mass")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_MASS));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Angle:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Angle")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Force:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Force")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Force")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_FORCE));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Roll rate:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Rollrate")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Total impulse:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Totalimpulse")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Totalimpulse")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Temperature:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Temperature")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Moment of inertia:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Momentofinertia")));
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Momentofinertia")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_INERTIA));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
|
||||
//// Pressure:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Pressure")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
|
||||
this.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Stability:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Stability")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
|
||||
this.add(combo, "sizegroup boxes");
|
||||
leftPanel.add(new JLabel(trans.get("pref.dlg.lbl.Stability")));
|
||||
combo = new JComboBox<>(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
|
||||
leftPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Secondary stability:
|
||||
final JLabel labelSecStab = new JLabel(trans.get("pref.dlg.lbl.SecondaryStability"));
|
||||
labelSecStab.setToolTipText(trans.get("pref.dlg.lbl.SecondaryStability.ttip"));
|
||||
labelSecStab.setEnabled(preferences.isDisplaySecondaryStability());
|
||||
leftPanel.add(labelSecStab);
|
||||
final JComboBox<?> comboSecStab = new JComboBox<>(new DefaultUnitSelector(UnitGroup.UNITS_SECONDARY_STABILITY));
|
||||
comboSecStab.setToolTipText(trans.get("pref.dlg.lbl.SecondaryStability.ttip"));
|
||||
comboSecStab.setEnabled(preferences.isDisplaySecondaryStability());
|
||||
leftPanel.add(comboSecStab, "sizegroup boxes, wrap");
|
||||
|
||||
//// Display secondary stability unit:
|
||||
JCheckBox displaySecondary = new JCheckBox(trans.get("pref.dlg.checkbox.DisplaySecondaryStability"));
|
||||
displaySecondary.setToolTipText(trans.get("pref.dlg.checkbox.DisplaySecondaryStability.ttip"));
|
||||
displaySecondary.setSelected(preferences.isDisplaySecondaryStability());
|
||||
displaySecondary.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
preferences.setDisplaySecondaryStability(e.getStateChange() == ItemEvent.SELECTED);
|
||||
labelSecStab.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
|
||||
comboSecStab.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
|
||||
}
|
||||
});
|
||||
leftPanel.add(displaySecondary, "spanx, wrap");
|
||||
|
||||
|
||||
// -------------- RIGHT PANEL
|
||||
|
||||
//// Line density:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Linedensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Surface density:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Surfacedensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Bulk density::
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Bulkdensity")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Surface roughness:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Surfaceroughness")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Area:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Area")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Angle:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Angle")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Roll rate:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Rollrate")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Temperature:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Temperature")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Pressure:
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Pressure")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
//// Windspeed:
|
||||
this.add(new JLabel(trans.get("pref.dlg.lbl.Windspeed")));
|
||||
rightPanel.add(new JLabel(trans.get("pref.dlg.lbl.Windspeed")));
|
||||
combo = new JComboBox<Object>(new DefaultUnitSelector(UnitGroup.UNITS_WINDSPEED));
|
||||
this.add(combo, "sizegroup boxes, wrap para");
|
||||
|
||||
rightPanel.add(combo, "sizegroup boxes, wrap");
|
||||
|
||||
this.add(leftPanel, "top");
|
||||
this.add(rightPanel, "top, wrap para");
|
||||
|
||||
|
||||
//// Default metric button
|
||||
|
@ -11,6 +11,7 @@ import java.awt.Rectangle;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
import net.sf.openrocket.logging.Warning;
|
||||
import net.sf.openrocket.logging.WarningSet;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
@ -31,6 +32,7 @@ import net.sf.openrocket.util.MathUtil;
|
||||
public class RocketInfo implements FigureElement {
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
private static final SwingPreferences preferences = (SwingPreferences) Application.getPreferences();
|
||||
// Margin around the figure edges, pixels
|
||||
private static final int MARGIN = 8;
|
||||
|
||||
@ -41,7 +43,8 @@ public class RocketInfo implements FigureElement {
|
||||
private final Caret cpCaret = new CPCaret(0,0);
|
||||
private final Caret cgCaret = new CGCaret(0,0);
|
||||
|
||||
private UnitGroup stabilityUnits;
|
||||
private UnitGroup.StabilityUnitGroup stabilityUnits;
|
||||
private UnitGroup.StabilityUnitGroup secondaryStabilityUnits;
|
||||
|
||||
private FlightConfiguration configuration;
|
||||
private double cg = 0, cp = 0;
|
||||
@ -66,6 +69,7 @@ public class RocketInfo implements FigureElement {
|
||||
public RocketInfo(FlightConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
this.stabilityUnits = UnitGroup.stabilityUnits(configuration);
|
||||
this.secondaryStabilityUnits = UnitGroup.secondaryStabilityUnits(configuration);
|
||||
}
|
||||
|
||||
|
||||
@ -189,19 +193,9 @@ public class RocketInfo implements FigureElement {
|
||||
|
||||
|
||||
private void drawStabilityInfo() {
|
||||
String at;
|
||||
//// at M=
|
||||
at = trans.get("RocketInfo.at")+UnitGroup.UNITS_COEFFICIENT.getDefaultUnit().toStringUnit(this.mach);
|
||||
if (!Double.isNaN(aoa)) {
|
||||
at += " "+ALPHA+"=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(aoa);
|
||||
}
|
||||
if (!Double.isNaN(theta)) {
|
||||
at += " "+THETA+"=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(theta);
|
||||
}
|
||||
|
||||
GlyphVector cgValue = createText(getCg());
|
||||
GlyphVector cpValue = createText(getCp());
|
||||
GlyphVector stabValue = createText(getStability());
|
||||
GlyphVector stabValue = createText(getStabilityCombined());
|
||||
|
||||
//// CG:
|
||||
GlyphVector cgText = createText(trans.get("RocketInfo.cgText"));
|
||||
@ -209,6 +203,15 @@ public class RocketInfo implements FigureElement {
|
||||
GlyphVector cpText = createText(trans.get("RocketInfo.cpText"));
|
||||
//// Stability:
|
||||
GlyphVector stabText = createText(trans.get("RocketInfo.stabText"));
|
||||
|
||||
//// at M=...
|
||||
String at = trans.get("RocketInfo.at")+UnitGroup.UNITS_COEFFICIENT.getDefaultUnit().toStringUnit(this.mach);
|
||||
if (!Double.isNaN(aoa)) {
|
||||
at += " "+ALPHA+"=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(aoa);
|
||||
}
|
||||
if (!Double.isNaN(theta)) {
|
||||
at += " "+THETA+"=" + UnitGroup.UNITS_ANGLE.getDefaultUnit().toStringUnit(theta);
|
||||
}
|
||||
GlyphVector atText = createSmallText(at);
|
||||
|
||||
// GlyphVector visual bounds drops the spaces, so we'll add them
|
||||
@ -277,13 +280,49 @@ public class RocketInfo implements FigureElement {
|
||||
return u.toStringUnit(massWithMotors);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the stability, in calibers.
|
||||
* Get the stability in both the selected stability unit and in percentage, e.g. "2.4 cal (14.1 %)".
|
||||
* If the current unit is already the percentage length unit, only use that.
|
||||
*
|
||||
* @return the current stability margin
|
||||
* @return the current stability margin in the currently selected stability unit and in percentage
|
||||
*/
|
||||
public String getStability () {
|
||||
return stabilityUnits.getDefaultUnit().toStringUnit(cp-cg);
|
||||
public String getStabilityCombined() {
|
||||
Unit stabilityUnit = stabilityUnits.getDefaultUnit();
|
||||
Unit secondaryStabilityUnit = secondaryStabilityUnits.getDefaultUnit();
|
||||
|
||||
String stabilityStr = getStability();
|
||||
|
||||
// Don't display secondary units if the stability is NaN, or if the secondary unit is the same as the primary unit,
|
||||
// or if it is disabled in the preferences
|
||||
if (Double.isNaN(getStabilityValue()) || secondaryStabilityUnit.equals(stabilityUnit) ||
|
||||
!preferences.isDisplaySecondaryStability()) {
|
||||
return stabilityStr;
|
||||
}
|
||||
|
||||
String secondaryStabilityStr = getSecondaryStability();
|
||||
|
||||
return stabilityStr + " / " + secondaryStabilityStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stability in the currently selected unit.
|
||||
* @return the current stability margin in the currently selected stability unit
|
||||
*/
|
||||
private String getStability() {
|
||||
return stabilityUnits.getDefaultUnit().toStringUnit(getStabilityValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stability in the secondary stability unit.
|
||||
* @return the current stability margin in the secondary stability unit
|
||||
*/
|
||||
private String getSecondaryStability() {
|
||||
return secondaryStabilityUnits.getDefaultUnit().toStringUnit(getStabilityValue());
|
||||
}
|
||||
|
||||
private double getStabilityValue() {
|
||||
return cp - cg;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -478,5 +517,6 @@ public class RocketInfo implements FigureElement {
|
||||
public void setCurrentConfig(FlightConfiguration newConfig) {
|
||||
this.configuration = newConfig;
|
||||
this.stabilityUnits = UnitGroup.stabilityUnits(newConfig);
|
||||
this.secondaryStabilityUnits = UnitGroup.secondaryStabilityUnits(newConfig);
|
||||
}
|
||||
}
|
||||
|
@ -1259,7 +1259,11 @@ public class BasicFrame extends JFrame {
|
||||
*/
|
||||
public static BasicFrame open(File file, Window parent) {
|
||||
OpenFileWorker worker = new OpenFileWorker(file);
|
||||
return open(worker, file.getName(), parent, false);
|
||||
BasicFrame frame = open(worker, file.getName(), parent, false);
|
||||
if (frame != null) {
|
||||
MRUDesignFile.getInstance().addFile(file.getAbsolutePath());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,8 +28,10 @@ import net.sf.openrocket.gui.util.Icons;
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
import net.sf.openrocket.gui.widgets.SaveFileChooser;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.simulation.FlightDataType;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.startup.Preferences;
|
||||
import net.sf.openrocket.util.Color;
|
||||
import net.sf.openrocket.gui.widgets.SelectColorButton;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
@ -72,6 +74,13 @@ public class SimulationPlotDialog extends JDialog {
|
||||
JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
|
||||
panel.add(label, "wrap");
|
||||
|
||||
// Add warning if X axis type is not time
|
||||
if (config.getDomainAxisType() != FlightDataType.TYPE_TIME) {
|
||||
JLabel msg = new StyledLabel(trans.get("PlotDialog.lbl.timeSeriesWarning"), -2);
|
||||
msg.setForeground(Color.DARK_RED.toAWTColor());
|
||||
panel.add(msg, "wrap");
|
||||
}
|
||||
|
||||
//// Show data points
|
||||
final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
|
||||
check.setSelected(initialShowPoints);
|
||||
|
@ -256,7 +256,7 @@ public class DesignReport {
|
||||
canvas.showText(text.getMassWithMotors(UnitGroup.UNITS_MASS.getDefaultUnit()));
|
||||
|
||||
canvas.newlineShowText(STABILITY);
|
||||
canvas.showText(text.getStability());
|
||||
canvas.showText(text.getStabilityCombined());
|
||||
|
||||
canvas.newlineShowText(CG);
|
||||
canvas.showText(text.getCg());
|
||||
|
@ -40,6 +40,7 @@ import net.sf.openrocket.simulation.FlightEvent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.startup.Preferences;
|
||||
import net.sf.openrocket.unit.Unit;
|
||||
import net.sf.openrocket.util.Color;
|
||||
import net.sf.openrocket.util.Utils;
|
||||
import net.sf.openrocket.gui.widgets.SelectColorButton;
|
||||
|
||||
@ -106,6 +107,7 @@ public class SimulationPlotPanel extends JPanel {
|
||||
|
||||
private int modifying = 0;
|
||||
|
||||
private DescriptionArea simPlotPanelDesc;
|
||||
|
||||
public SimulationPlotPanel(final Simulation simulation) {
|
||||
super(new MigLayout("fill"));
|
||||
@ -170,6 +172,14 @@ public class SimulationPlotPanel extends JPanel {
|
||||
if (modifying > 0)
|
||||
return;
|
||||
FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
|
||||
if (type == FlightDataType.TYPE_TIME) {
|
||||
simPlotPanelDesc.setVisible(false);
|
||||
simPlotPanelDesc.setText("");
|
||||
}
|
||||
else {
|
||||
simPlotPanelDesc.setVisible(true);
|
||||
simPlotPanelDesc.setText(trans.get("simplotpanel.Desc"));
|
||||
}
|
||||
configuration.setDomainAxisType(type);
|
||||
domainUnitSelector.setUnitGroup(type.getUnitGroup());
|
||||
domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
|
||||
@ -193,9 +203,12 @@ public class SimulationPlotPanel extends JPanel {
|
||||
this.add(domainUnitSelector, "width 40lp, gapright para");
|
||||
|
||||
//// The data will be plotted in time order even if the X axis type is not time.
|
||||
DescriptionArea desc = new DescriptionArea(trans.get("simplotpanel.Desc"), 2, -2f);
|
||||
desc.setViewportBorder(BorderFactory.createEmptyBorder());
|
||||
this.add(desc, "width 1px, growx 1, wrap unrel");
|
||||
simPlotPanelDesc = new DescriptionArea("", 2, -2f, false);
|
||||
simPlotPanelDesc.setVisible(false);
|
||||
simPlotPanelDesc.setForeground(Color.DARK_RED.toAWTColor());
|
||||
simPlotPanelDesc.setViewportBorder(BorderFactory.createEmptyBorder());
|
||||
this.add(simPlotPanelDesc, "width 1px, growx 1, wrap unrel");
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -121,25 +121,32 @@ public abstract class PreferencesExporter {
|
||||
keysToIgnore.add(SwingPreferences.UPDATE_PLATFORM); // Don't export platform-specific settings
|
||||
}
|
||||
|
||||
private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException {
|
||||
public static void exportPreferencesToFile(Preferences preferences, FileOutputStream fos, boolean filterPreferences)
|
||||
throws BackingStoreException, IOException {
|
||||
// If no filtering is required, just export the preferences
|
||||
if (!filterPreferences) {
|
||||
preferences.exportSubtree(fos);
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out user directories
|
||||
Preferences root = Preferences.userRoot();
|
||||
String originalNodeName = ((SwingPreferences) prefs).getNodename();
|
||||
String nodeName = originalNodeName + "-temp";
|
||||
if (root.nodeExists(nodeName)) {
|
||||
root.node(nodeName).removeNode();
|
||||
String filteredPrefsNodeName = originalNodeName + "-filtered";
|
||||
if (root.nodeExists(filteredPrefsNodeName)) {
|
||||
root.node(filteredPrefsNodeName).removeNode();
|
||||
}
|
||||
Preferences tempPrefs = root.node(nodeName);
|
||||
Preferences filteredPrefs = root.node(filteredPrefsNodeName);
|
||||
|
||||
// Fill in all parameters to the temporary preferences, except for user directories
|
||||
copyFilteredPreferences(preferences, tempPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore);
|
||||
copyFilteredPreferences(preferences, filteredPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore);
|
||||
|
||||
// Export the filtered preferences
|
||||
try {
|
||||
// Export the filtered preferences to a temporary file
|
||||
Path tempFile = Files.createTempFile("ORprefs_" + System.currentTimeMillis(), ".xml");
|
||||
Path tempFile = Files.createTempFile("ORPrefs_" + System.currentTimeMillis(), ".xml");
|
||||
try (FileOutputStream tempFos = new FileOutputStream(tempFile.toFile())) {
|
||||
tempPrefs.exportSubtree(tempFos);
|
||||
filteredPrefs.exportSubtree(tempFos);
|
||||
}
|
||||
|
||||
// Read and parse the temporary file
|
||||
@ -149,11 +156,11 @@ public abstract class PreferencesExporter {
|
||||
doc = factory.newDocumentBuilder().parse(tempFis);
|
||||
}
|
||||
|
||||
// Find and rename the node
|
||||
// Find and rename the filtered prefs node
|
||||
NodeList nodeList = doc.getElementsByTagName("node");
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Element element = (Element) nodeList.item(i);
|
||||
if (element.getAttribute("name").equals(nodeName)) {
|
||||
if (element.getAttribute("name").equals(filteredPrefsNodeName)) {
|
||||
element.setAttribute("name", ((SwingPreferences) prefs).getNodename());
|
||||
break;
|
||||
}
|
||||
@ -176,10 +183,14 @@ public abstract class PreferencesExporter {
|
||||
} catch (ParserConfigurationException | TransformerException | SAXException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
root.node(nodeName).removeNode();
|
||||
root.node(filteredPrefsNodeName).removeNode();
|
||||
}
|
||||
}
|
||||
|
||||
private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException {
|
||||
exportPreferencesToFile(preferences, fos, true);
|
||||
}
|
||||
|
||||
private static void copyFilteredPreferences(Preferences src, Preferences dest,
|
||||
List<String> nodesToIgnore, List<String> keysToIgnore, List<String> prefixKeysToIgnore) throws BackingStoreException {
|
||||
for (String key : src.keys()) {
|
||||
|
@ -17,6 +17,11 @@ public abstract class PreferencesImporter {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
private static final Logger log = LoggerFactory.getLogger(PreferencesImporter.class);
|
||||
|
||||
/**
|
||||
* Import the preferences by first showing a file chooser dialog to locate the preferences file.
|
||||
* @param parent The parent window for the file chooser dialog.
|
||||
* @return true if the preferences were imported successfully, false otherwise.
|
||||
*/
|
||||
public static boolean importPreferences(Window parent) {
|
||||
final JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle(trans.get("PreferencesImporter.chooser.title"));
|
||||
@ -33,13 +38,26 @@ public abstract class PreferencesImporter {
|
||||
((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
|
||||
|
||||
File importFile = chooser.getSelectedFile();
|
||||
return importPreferences(importFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import preferences from an XML file.
|
||||
* @param importFile The XML file to import preferences from.
|
||||
* @return true if the preferences were imported successfully, false otherwise.
|
||||
*/
|
||||
public static boolean importPreferences(File importFile) {
|
||||
try (FileInputStream fis = new FileInputStream(importFile)) {
|
||||
Preferences.importPreferences(fis);
|
||||
|
||||
// Ensure units are updated
|
||||
((SwingPreferences) Application.getPreferences()).loadDefaultUnits();
|
||||
|
||||
log.info("Preferences imported successfully.");
|
||||
return true;
|
||||
} catch (IOException | InvalidPreferencesFormatException e) {
|
||||
log.warn("Error while importing preferences: " + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -626,6 +626,9 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
|
||||
|
||||
///////// Default unit storage
|
||||
|
||||
/**
|
||||
* Loads the default units from the preferences.
|
||||
*/
|
||||
public void loadDefaultUnits() {
|
||||
Preferences prefs = PREFNODE.node("units");
|
||||
try {
|
||||
@ -646,6 +649,9 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the standard default units in the preferences.
|
||||
*/
|
||||
public void storeDefaultUnits() {
|
||||
Preferences prefs = PREFNODE.node("units");
|
||||
|
||||
|
@ -6,8 +6,10 @@ import java.awt.desktop.OpenFilesHandler;
|
||||
import java.awt.desktop.PreferencesHandler;
|
||||
import java.awt.desktop.QuitHandler;
|
||||
import java.awt.desktop.AppReopenedListener;
|
||||
import java.io.File;
|
||||
|
||||
import net.sf.openrocket.communication.UpdateInfoRetriever;
|
||||
import net.sf.openrocket.gui.main.MRUDesignFile;
|
||||
import net.sf.openrocket.gui.util.DummyFrameMenuOSX;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -40,8 +42,9 @@ final class OSXSetup {
|
||||
* The handler for file associations
|
||||
*/
|
||||
public static final OpenFilesHandler OPEN_FILE_HANDLER = (e) -> {
|
||||
log.info("Opening file from association: " + e.getFiles().get(0));
|
||||
BasicFrame.open(e.getFiles().get(0), BasicFrame.lastFrameInstance);
|
||||
File associateFile = e.getFiles().get(0);
|
||||
log.info("Opening file from association: " + associateFile);
|
||||
BasicFrame.open(associateFile, BasicFrame.lastFrameInstance);
|
||||
};
|
||||
|
||||
/**
|
||||
|