From acc20ee772598b3707f4fab3e659b7fe8a3a2bed Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 21 Mar 2023 23:46:28 +0100 Subject: [PATCH 01/76] Small cleanups --- .../net/sf/openrocket/database/Databases.java | 2 +- .../sf/openrocket/preset/xml/MaterialDTO.java | 34 +++++++++---------- .../preset/xml/MaterialTypeDTO.java | 3 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/core/src/net/sf/openrocket/database/Databases.java b/core/src/net/sf/openrocket/database/Databases.java index a2e9fb744..339315795 100644 --- a/core/src/net/sf/openrocket/database/Databases.java +++ b/core/src/net/sf/openrocket/database/Databases.java @@ -222,7 +222,7 @@ public class Databases { * the provided name if unable to do so. * * @param type the material type. - * @param baseName the base name of the material. + * @param baseName the base name of the material. * @param density the density of the material. * @return the material object from the database or a new material. */ diff --git a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java index f7499ff9c..8eeea3d94 100644 --- a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java +++ b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java @@ -18,7 +18,7 @@ import net.sf.openrocket.util.Chars; @XmlRootElement(name = "Material") @XmlAccessorType(XmlAccessType.FIELD) public class MaterialDTO { - + @XmlElement(name = "Name") private String name; @XmlElement(name = "Density") @@ -27,65 +27,65 @@ public class MaterialDTO { private MaterialTypeDTO type; @XmlAttribute(name = "UnitsOfMeasure") private String uom; - + /** * Default constructor. */ public MaterialDTO() { } - + public MaterialDTO(final Material theMaterial) { this(theMaterial.getName(), theMaterial.getDensity(), MaterialTypeDTO.asDTO(theMaterial.getType()), theMaterial.getType().getUnitGroup().getDefaultUnit().toString()); } - + public MaterialDTO(final String theName, final double theDensity, final MaterialTypeDTO theType, final String theUom) { name = theName; density = theDensity; type = theType; uom = theUom; } - + public String getName() { return name; } - + public void setName(final String theName) { name = theName; } - + public double getDensity() { return density; } - + public void setDensity(final double theDensity) { density = theDensity; } - + public MaterialTypeDTO getType() { return type; } - + public void setType(final MaterialTypeDTO theType) { type = theType; } - + public String getUom() { return uom; } - + public void setUom(final String theUom) { uom = theUom; } - + Material asMaterial() { return Databases.findMaterial(type.getORMaterialType(), name, density); } - - + + /** * Special directive to the JAXB system. After the object is parsed from xml, - * we replace the '2' with Chars.SQUARED, and '3' with Chars.CUBED. Just the + * we replace the '2' with Chars.SQUARED, and '3' with Chars.CUBED. Just the * opposite transformation as done in beforeMarshal. * @param unmarshaller * @param parent @@ -97,7 +97,7 @@ public class MaterialDTO { uom = uom.replace('3', Chars.CUBED); } } - + /** * Special directive to the JAXB system. Before the object is serialized into xml, * we strip out the special unicode characters for cubed and squared so they appear diff --git a/core/src/net/sf/openrocket/preset/xml/MaterialTypeDTO.java b/core/src/net/sf/openrocket/preset/xml/MaterialTypeDTO.java index acc23d31f..8af0ab35c 100644 --- a/core/src/net/sf/openrocket/preset/xml/MaterialTypeDTO.java +++ b/core/src/net/sf/openrocket/preset/xml/MaterialTypeDTO.java @@ -20,8 +20,7 @@ public enum MaterialTypeDTO { public static MaterialTypeDTO asDTO(Material.Type targetType) { MaterialTypeDTO[] values = values(); - for (int i = 0; i < values.length; i++) { - MaterialTypeDTO value = values[i]; + for (MaterialTypeDTO value : values) { if (value.corollary.equals(targetType)) { return value; } From 16ee787f6658b6bd67f1a97db81f32c8d79533dd Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 22 Mar 2023 11:45:20 +0100 Subject: [PATCH 02/76] WIP --- core/src/net/sf/openrocket/preset/xml/MaterialDTO.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java index 8eeea3d94..6694dbb7d 100644 --- a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java +++ b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java @@ -44,6 +44,7 @@ public class MaterialDTO { density = theDensity; type = theType; uom = theUom; + type.getORMaterialType().getUnitGroup().setDefaultUnit(getUom()); } public String getName() { @@ -68,6 +69,7 @@ public class MaterialDTO { public void setType(final MaterialTypeDTO theType) { type = theType; + type.getORMaterialType().getUnitGroup().setDefaultUnit(getUom()); } public String getUom() { @@ -76,6 +78,7 @@ public class MaterialDTO { public void setUom(final String theUom) { uom = theUom; + type.getORMaterialType().getUnitGroup().setDefaultUnit(theUom); } Material asMaterial() { From 05b6611730a2d2514396d9801d441b5bf11149a0 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 25 Mar 2023 00:18:10 +0100 Subject: [PATCH 03/76] Change key names --- .../file/rasaero/importt/BodyTubeHandler.java | 4 ++-- .../file/rasaero/importt/BoosterHandler.java | 6 ++--- .../file/rasaero/importt/FinCanHandler.java | 5 ++--- .../file/rasaero/importt/FinHandler.java | 12 +++++----- .../rasaero/importt/LaunchLugHandler.java | 4 ++-- .../rasaero/importt/LaunchSiteHandler.java | 10 ++++----- .../file/rasaero/importt/NoseConeHandler.java | 4 ++-- .../importt/RASAeroCommonConstants.java | 22 +++++++++---------- .../rasaero/importt/RailGuideHandler.java | 4 ++-- .../file/rasaero/importt/RecoveryHandler.java | 4 ++-- .../rasaero/importt/TransitionHandler.java | 6 ++--- 11 files changed, 40 insertions(+), 41 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java index e43c9b33b..44ea54ac0 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java @@ -75,8 +75,8 @@ public class BodyTubeHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.bodyTube.setLength(length / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); - this.bodyTube.setOuterRadius(diameter/2 / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); // Not really useful, but included for completeness + this.bodyTube.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + this.bodyTube.setOuterRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); // Not really useful, but included for completeness this.bodyTube.setOuterRadiusAutomatic(true); this.bodyTube.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java index b01f1d886..9b27a7c55 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java @@ -51,11 +51,11 @@ public class BoosterHandler extends BodyTubeHandler { public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.closeElement(element, attributes, content, warnings); if (RASAeroCommonConstants.BOAT_TAIL_LENGTH.equals(element)) { - this.boatTailLength = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + this.boatTailLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; } else if (RASAeroCommonConstants.BOAT_TAIL_REAR_DIAMETER.equals(element)) { - this.boatTailRearDiameter = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + this.boatTailRearDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; } else if (RASAeroCommonConstants.SHOULDER_LENGTH.equals(element)) { - this.shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + this.shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java index 6a23d0fc4..353f1a3b6 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java @@ -2,7 +2,6 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; -import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -60,9 +59,9 @@ public class FinCanHandler extends BodyTubeHandler { super.closeElement(element, attributes, content, warnings); try { if (RASAeroCommonConstants.INSIDE_DIAMETER.equals(element)) { - insideDiameter = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + insideDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; } else if (RASAeroCommonConstants.SHOULDER_LENGTH.equals(element)) { - shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java index e184e3e48..a0aca4fcb 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java @@ -46,21 +46,21 @@ public class FinHandler extends AbstractElementHandler { if (RASAeroCommonConstants.FIN_COUNT.equals(element)) { finSet.setFinCount(Integer.parseInt(content)); } else if (RASAeroCommonConstants.FIN_CHORD.equals(element)) { - finSet.setRootChord(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + finSet.setRootChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } else if (RASAeroCommonConstants.FIN_SPAN.equals(element)) { - finSet.setHeight(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + finSet.setHeight(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } else if (RASAeroCommonConstants.FIN_SWEEP_DISTANCE.equals(element)) { - finSet.setSweep(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + finSet.setSweep(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } else if (RASAeroCommonConstants.FIN_TIP_CHORD.equals(element)) { - finSet.setTipChord(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + finSet.setTipChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } else if (RASAeroCommonConstants.FIN_THICKNESS.equals(element)) { - finSet.setThickness(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + finSet.setThickness(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } else if (RASAeroCommonConstants.AIRFOIL_SECTION.equals(element)) { finSet.setCrossSection(RASAeroCommonConstants.getFinCrossSectionFromRASAero(content, warnings)); } else if (RASAeroCommonConstants.LOCATION.equals(element)) { // Location is the location of the front of the fin relative to the bottom of the body tube finSet.setAxialMethod(AxialMethod.BOTTOM); - double location = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + double location = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; location = -location + finSet.getLength(); finSet.setAxialOffset(location); } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java index 85747c29a..66713f85d 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java @@ -14,8 +14,8 @@ import net.sf.openrocket.rocketcomponent.position.AxialMethod; */ public abstract class LaunchLugHandler { public static void addLaunchLug(BodyTube parent, double diameter, double length) { - diameter = diameter / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; - length = length / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + length = length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; LaunchLug lug = generateLaunchLugFromRASAeroRailGuide(diameter, length, parent.getLength()); parent.addChild(lug); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java index 77e112603..d8145e260 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java @@ -36,18 +36,18 @@ public class LaunchSiteHandler extends AbstractElementHandler { public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { try { if (RASAeroCommonConstants.LAUNCH_ALTITUDE.equals(element)) { - launchSiteSettings.setLaunchAltitude(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE); + launchSiteSettings.setLaunchAltitude(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); } else if (RASAeroCommonConstants.LAUNCH_PRESSURE.equals(element)) { - launchSiteSettings.setLaunchPressure(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_PRESSURE); + launchSiteSettings.setLaunchPressure(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_PRESSURE); } else if (RASAeroCommonConstants.LAUNCH_ROD_ANGLE.equals(element)) { - launchSiteSettings.setLaunchRodAngle(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ANGLE); + launchSiteSettings.setLaunchRodAngle(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ANGLE); } else if (RASAeroCommonConstants.LAUNCH_ROD_LENGTH.equals(element)) { - launchSiteSettings.setLaunchRodLength(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE); + launchSiteSettings.setLaunchRodLength(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); } else if (RASAeroCommonConstants.LAUNCH_TEMPERATURE.equals(element)) { launchSiteSettings.setLaunchTemperature( RASAeroCommonConstants.RASAERO_TO_OPENROCKET_TEMPERATURE(Double.parseDouble(content))); } else if (RASAeroCommonConstants.LAUNCH_WIND_SPEED.equals(element)) { - launchSiteSettings.setWindSpeedAverage(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SPEED); + launchSiteSettings.setWindSpeedAverage(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SPEED); } } catch (NumberFormatException e) { warnings.add("Invalid number format for element " + element + ", ignoring."); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java index 24866c642..84114e199 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java @@ -63,8 +63,8 @@ public class NoseConeHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.noseCone.setLength(length / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); - this.noseCone.setBaseRadius(diameter/2 / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + this.noseCone.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + this.noseCone.setBaseRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); this.noseCone.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java index f5e487a70..8d69828e8 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java @@ -124,27 +124,27 @@ public class RASAeroCommonConstants { /** - * Length conversion. RASAero is in inches, OpenRocket in meters. + * Length conversion from OpenRocket units to RASAero units. RASAero is in inches, OpenRocket in meters. */ - public static final double RASAERO_TO_OPENROCKET_LENGTH = 39.37; + public static final double OPENROCKET_TO_RASAERO_TO_LENGTH = 39.37; /** - * Altitude conversion. RASAero is in feet, OpenRocket in meters. + * Altitude conversion from OpenRocket units to RASAero units. RASAero is in feet, OpenRocket in meters. */ - public static final double RASAERO_TO_OPENROCKET_ALTITUDE = 3.28084; + public static final double OPENROCKET_TO_RASAERO_ALTITUDE = 3.28084; /** - * Speed conversion. RASAero is in mph, OpenRocket in m/s. + * Speed conversion from OpenRocket units to RASAero units. RASAero is in mph, OpenRocket in m/s. */ - public static final double RASAERO_TO_OPENROCKET_SPEED = 2.23694; + public static final double OPENROCKET_TO_RASAERO_SPEED = 2.23694; /** - * Pressure conversion. RASAero is in in-hg, OpenRocket in Pa. + * Pressure conversion from OpenRocket units to RASAero units. RASAero is in in-hg, OpenRocket in Pa. */ - public static final double RASAERO_TO_OPENROCKET_PRESSURE = 0.000295301; + public static final double OPENROCKET_TO_RASAERO_PRESSURE = 0.000295301; /** - * Angle conversion. RASAero is in degrees, OpenRocket in rad. + * Angle conversion from OpenRocket units to RASAero units. RASAero is in degrees, OpenRocket in rad. */ - public static final double RASAERO_TO_OPENROCKET_ANGLE = 180 / Math.PI; + public static final double OPENROCKET_TO_RASAERO_ANGLE = 180 / Math.PI; /** - * Temperature conversion. RASAero is in Fahrenheit, OpenRocket in Kelvin. + * Temperature conversion from OpenRocket units to RASAero units. RASAero is in Fahrenheit, OpenRocket in Kelvin. */ public static final double RASAERO_TO_OPENROCKET_TEMPERATURE(Double input) { return (input + 459.67) * 5.0 / 9.0; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java index 432ea662d..6b7217174 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java @@ -22,8 +22,8 @@ public abstract class RailGuideHandler { * @param height total height of the rail guide, plus the screw height */ public static void addRailGuide(BodyTube parent, double diameter, double height) { - diameter = diameter / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; - height = height / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + height = height / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; RailButton button = generateRailButtonFromRASAeroRailGuide(diameter, height, parent.getLength()); parent.addChild(button); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java index 01cd4411a..39f6a9768 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java @@ -163,10 +163,10 @@ public class RecoveryHandler extends AbstractElementHandler { recoveryDevice.setName("Recovery Event " + (recoveryDeviceNr+1)); DeploymentConfiguration config = recoveryDevice.getDeploymentConfigurations().getDefault(); - recoveryDevice.setDiameter(size / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + recoveryDevice.setDiameter(size / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); recoveryDevice.setLineLength(recoveryDevice.getDiameter()); recoveryDevice.setCD(CD); - config.setDeployAltitude(altitude / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE); + config.setDeployAltitude(altitude / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); // There is a special RASAero rule: if event 1 AND event 2 are set to apogee, then set event 2 to altitude if (recoveryDeviceNr == 1 && eventType.equals("Apogee") && this.eventType[0].equals("Apogee")) { diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java index 88660244a..6f9893cc3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java @@ -54,7 +54,7 @@ public class TransitionHandler extends BaseHandler { super.closeElement(element, attributes, content, warnings); try { if (RASAeroCommonConstants.REAR_DIAMETER.equals(element)) { - this.transition.setAftRadius(Double.parseDouble(content) / 2 / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); + this.transition.setAftRadius(Double.parseDouble(content) / 2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); @@ -64,8 +64,8 @@ public class TransitionHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.transition.setLength(length / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); - this.transition.setForeRadius(diameter/2 / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH); // Not really useful, but adding it for completeness + this.transition.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + this.transition.setForeRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); // Not really useful, but adding it for completeness this.transition.setForeRadiusAutomatic(true); this.transition.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this } From 41a045038ed4ebf1e95eac5c24f7c087cbf7c6d9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 26 Mar 2023 21:33:26 +0200 Subject: [PATCH 04/76] [WIP] Implement RASAero exporting --- core/resources/l10n/messages.properties | 7 +- core/resources/l10n/messages_nl.properties | 5 + core/resources/l10n/messages_ru.properties | 5 + .../openrocket/file/GeneralRocketSaver.java | 13 +- .../net/sf/openrocket/file/RocketSaver.java | 2 + .../file/rasaero/CustomBooleanAdapter.java | 19 ++ .../file/rasaero/CustomDoubleAdapter.java | 22 ++ .../{importt => }/RASAeroCommonConstants.java | 198 ++++++++++-- .../file/rasaero/export/BasePartDTO.java | 133 ++++++++ .../file/rasaero/export/BodyTubeDTO.java | 156 ++++++++++ .../rasaero/export/BodyTubeDTOAdapter.java | 90 ++++++ .../file/rasaero/export/BoosterDTO.java | 285 ++++++++++++++++++ .../file/rasaero/export/FinDTO.java | 160 ++++++++++ .../file/rasaero/export/LaunchSiteDTO.java | 4 + .../file/rasaero/export/NoseConeDTO.java | 70 +++++ .../rasaero/export/RASAeroDocumentDTO.java | 87 ++++++ .../file/rasaero/export/RASAeroSaver.java | 93 ++++++ .../file/rasaero/export/RecoveryDTO.java | 4 + .../file/rasaero/export/RocketDesignDTO.java | 202 +++++++++++++ .../rasaero/export/SimulationListDTO.java | 4 + .../file/rasaero/export/TransitionDTO.java | 61 ++++ .../file/rasaero/importt/BaseHandler.java | 1 + .../file/rasaero/importt/BoattailHandler.java | 1 + .../file/rasaero/importt/BodyTubeHandler.java | 5 +- .../file/rasaero/importt/BoosterHandler.java | 13 +- .../file/rasaero/importt/FinCanHandler.java | 5 +- .../file/rasaero/importt/FinHandler.java | 15 +- .../rasaero/importt/LaunchLugHandler.java | 5 +- .../rasaero/importt/LaunchSiteHandler.java | 1 + .../file/rasaero/importt/NoseConeHandler.java | 9 +- .../file/rasaero/importt/RASAeroHandler.java | 1 + .../rasaero/importt/RailGuideHandler.java | 5 +- .../file/rasaero/importt/RecoveryHandler.java | 5 +- .../importt/SimulationListHandler.java | 1 + .../rasaero/importt/SurfaceFinishHandler.java | 3 +- .../rasaero/importt/TransitionHandler.java | 7 +- .../sf/openrocket/startup/Preferences.java | 9 + core/src/net/sf/openrocket/util/Color.java | 13 +- .../preferences/GeneralPreferencesPanel.java | 11 + .../sf/openrocket/gui/main/BasicFrame.java | 134 ++++++-- .../gui/main/DesignFileSaveAsFileChooser.java | 4 +- 41 files changed, 1777 insertions(+), 91 deletions(-) create mode 100644 core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java rename core/src/net/sf/openrocket/file/rasaero/{importt => }/RASAeroCommonConstants.java (54%) create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 88290ee6d..3d8cff127 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -289,6 +289,7 @@ pref.dlg.tab.Design = Design pref.dlg.tab.Simulation = Simulation 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.but.clearCachedPreferences = Reset all preferences pref.dlg.but.clearCachedPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.) @@ -1340,7 +1341,11 @@ TransitionCfg.tab.Generalproperties = General properties TransitionCfg.tab.Shoulder = Shoulder TransitionCfg.tab.Shoulderproperties = Shoulder properties -! Save RKT Warning Dialog +! Save RASAero Warning Dialog +SaveRASAeroWarningDialog.txt1 = Exporting to RASAero file format does not support all features of OpenRocket. +SaveRASAeroWarningDialog.donotshow = Do not show this dialog again + +! Save RockSim Warning Dialog SaveRktWarningDialog.txt1=Exporting to RockSim file format does not support all features of OpenRocket. SaveRktWarningDialog.donotshow=Do not show this dialog again diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties index a2de9a92c..580477fbf 100644 --- a/core/resources/l10n/messages_nl.properties +++ b/core/resources/l10n/messages_nl.properties @@ -276,6 +276,7 @@ pref.dlg.tab.Design = Ontwerp pref.dlg.tab.Simulation = Simulatie pref.dlg.tab.Launch = Lanceer pref.dlg.tab.Miscellaneousoptions = Diverse opties +pref.dlg.lbl.RASAeroWarning = Toon waarschuwingen bij opslaan in RASAero-formaat pref.dlg.lbl.RockSimWarning = Toon waarschuwingen bij opslaan in RockSim-formaat pref.dlg.but.clearCachedPreferences = Alle voorkeuren opnieuw instellen pref.dlg.but.clearCachedPreferences.ttip = Alle voorkeuren opnieuw instellen, inclusief voorkeuren in de cache (UI-instellingen, recente bestanden, enz.) @@ -1219,6 +1220,10 @@ FinsetConfig.ttip.Finfillets1 = Voegt de voorspelde massa van de vin fille FinsetConfig.ttip.Finfillets2 = Veronderstelt dat de fillet concaaf is en raakt aan de buis en de vin.
FinsetConfig.ttip.Finfillets3 = Nul radius geeft geen fillet. +! Save RASAero Warning Dialog +SaveRASAeroWarningDialog.txt1 = Exporteren naar RASAero bestandsformaat ondersteunt niet alle functies van OpenRocket. +SaveRASAeroWarningDialog.donotshow = Laat dit dialoogvenster niet meer zien + ! Save RKT Warning Dialog SaveRktWarningDialog.txt1 = Exporteren naar RockSim bestandsformaat ondersteunt niet alle functies van OpenRocket. SaveRktWarningDialog.donotshow = Laat dit dialoogvenster niet meer zien diff --git a/core/resources/l10n/messages_ru.properties b/core/resources/l10n/messages_ru.properties index 4cc9d0b6b..57de96180 100644 --- a/core/resources/l10n/messages_ru.properties +++ b/core/resources/l10n/messages_ru.properties @@ -276,6 +276,7 @@ pref.dlg.tab.Design = \u0414\u0438\u0437\u0430\u0439\u043D pref.dlg.tab.Simulation = \u0420\u0430\u0441\u0447\u0435\u0442 pref.dlg.tab.Launch = \u0417\u0430\u043F\u0443\u0441\u043A pref.dlg.tab.Miscellaneousoptions = \u041F\u0440\u043E\u0447\u0438\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 +pref.dlg.lbl.RASAeroWarning = \u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0438 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442\u0435 RASAero pref.dlg.lbl.RockSimWarning = \u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0438 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442\u0435 RockSim pref.dlg.tab.Graphics = \u0413\u0440\u0430\u0444\u0438\u043A\u0430 @@ -1267,6 +1268,10 @@ FinsetConfig.ttip.Finfillets1 = \u0414\u043E\u0431\u0430\u0432\u043B\u044F FinsetConfig.ttip.Finfillets2 = \u041F\u0440\u0435\u0434\u043F\u043E\u043B\u0430\u0433\u0430\u0435\u0442\u0441\u044F, \u0447\u0442\u043E \u0433\u0430\u043B\u0442\u0435\u043B\u044C \u043A\u0430\u0441\u0430\u0435\u0442\u0441\u044F \u0442\u0440\u0443\u0431\u044B \u043A\u043E\u0440\u043F\u0443\u0441\u0430 \u0438 \u0440\u0435\u0431\u0440\u0430.
FinsetConfig.ttip.Finfillets3 = \u041D\u0443\u043B\u0435\u0432\u043E\u0439 \u0440\u0430\u0434\u0438\u0443\u0441 \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0433\u0430\u043B\u0442\u0435\u043B\u0435\u0439 \u043D\u0435\u0442. +! Save RASAero Warning Dialog +SaveRASAeroWarningDialog.txt1 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442 \u0444\u0430\u0439\u043B\u0430 RASAero \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u0438 OpenRocket. +SaveRASAeroWarningDialog.donotshow = \u0411\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u044D\u0442\u043E\u0442 \u0434\u0438\u0430\u043B\u043E\u0433 + ! Save RKT Warning Dialog SaveRktWarningDialog.txt1 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442 \u0444\u0430\u0439\u043B\u0430 RockSim \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u0438 OpenRocket. SaveRktWarningDialog.donotshow = \u0411\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u044D\u0442\u043E\u0442 \u0434\u0438\u0430\u043B\u043E\u0433 diff --git a/core/src/net/sf/openrocket/file/GeneralRocketSaver.java b/core/src/net/sf/openrocket/file/GeneralRocketSaver.java index 511e670b2..a327e3be5 100644 --- a/core/src/net/sf/openrocket/file/GeneralRocketSaver.java +++ b/core/src/net/sf/openrocket/file/GeneralRocketSaver.java @@ -19,6 +19,7 @@ import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; import net.sf.openrocket.document.StorageOptions.FileType; import net.sf.openrocket.file.openrocket.OpenRocketSaver; +import net.sf.openrocket.file.rasaero.export.RASAeroSaver; import net.sf.openrocket.file.rocksim.export.RockSimSaver; import net.sf.openrocket.rocketcomponent.InsideColorComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -142,8 +143,10 @@ public class GeneralRocketSaver { * @return the estimated number of bytes the storage would take. */ public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) { - if (options.getFileType() == StorageOptions.FileType.ROCKSIM) { + if (options.getFileType() == FileType.ROCKSIM) { return new RockSimSaver().estimateFileSize(doc, options); + } else if (options.getFileType() == FileType.RASAERO) { + return new RASAeroSaver().estimateFileSize(doc, options); } else { return new OpenRocketSaver().estimateFileSize(doc, options); } @@ -151,10 +154,10 @@ public class GeneralRocketSaver { private void save(String fileName, OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException, DecalNotFoundException { - // For now, we don't save decal information in ROCKSIM files, so don't do anything + // For now, we don't save decal information in ROCKSIM/RASAero files, so don't do anything // which follows. // TODO - add support for decals in ROCKSIM files? - if (options.getFileType() == FileType.ROCKSIM) { + if (options.getFileType() == FileType.ROCKSIM || options.getFileType() == FileType.RASAERO) { saveInternal(output, document, options); output.close(); return; @@ -232,8 +235,10 @@ public class GeneralRocketSaver { private void saveInternal(OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException { - if (options.getFileType() == StorageOptions.FileType.ROCKSIM) { + if (options.getFileType() == FileType.ROCKSIM) { new RockSimSaver().save(output, document, options); + } else if (options.getFileType() == FileType.RASAERO) { + new RASAeroSaver().save(output, document, options); } else { new OpenRocketSaver().save(output, document, options); } diff --git a/core/src/net/sf/openrocket/file/RocketSaver.java b/core/src/net/sf/openrocket/file/RocketSaver.java index b731c7f75..1ccb0e41b 100644 --- a/core/src/net/sf/openrocket/file/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/RocketSaver.java @@ -3,11 +3,13 @@ package net.sf.openrocket.file; import java.io.IOException; import java.io.OutputStream; +import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; public abstract class RocketSaver { + protected final WarningSet warnings = new WarningSet(); /** * Save the document to the specified output stream using the default storage options. diff --git a/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java b/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java new file mode 100644 index 000000000..336f326ed --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java @@ -0,0 +1,19 @@ +package net.sf.openrocket.file.rasaero; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public class CustomBooleanAdapter extends XmlAdapter { + + @Override + public Boolean unmarshal(String s) throws Exception { + return "true".equalsIgnoreCase(s); + } + + @Override + public String marshal(Boolean b) throws Exception { + if (b) { + return "True"; + } + return "False"; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java b/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java new file mode 100644 index 000000000..77b600728 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.file.rasaero; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +public class CustomDoubleAdapter extends XmlAdapter { + @Override + public Double unmarshal(String s) throws Exception { + return Double.parseDouble(s); + } + + @Override + public String marshal(Double aDouble) throws Exception { + if (aDouble == null) { + return null; + } + DecimalFormat df = new DecimalFormat("#.####", new DecimalFormatSymbols(Locale.US)); // RASAero has 4 decimal precision + return df.format(aDouble); + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java similarity index 54% rename from core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java rename to core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index 8d69828e8..d42161a64 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -1,25 +1,36 @@ -package net.sf.openrocket.file.rasaero.importt; +package net.sf.openrocket.file.rasaero; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.Color; +import net.sf.openrocket.util.MathUtil; import java.util.HashMap; import java.util.Map; +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; + /** * List of constants used in RASAero files + helper functions to read parameters from it. * * @author Sibo Van Gool */ public class RASAeroCommonConstants { + // File settings + public static final String FILE_EXTENSION = "CDX1"; + // General settings public static final String RASAERO_DOCUMENT = "RASAeroDocument"; public static final String FILE_VERSION = "FileVersion"; public static final String ROCKET_DESIGN = "RocketDesign"; + // RASAeroDocument settings + public static final String MACH_ALT = "MachAlt"; + + // Base part settings public static final String PART_TYPE = "PartType"; public static final String LENGTH = "Length"; public static final String DIAMETER = "Diameter"; @@ -37,12 +48,24 @@ public class RASAeroCommonConstants { public static final String FIN_CAN = "FinCan"; public static final String BOATTAIL = "BoatTail"; + // Body tube settings + public static final String OVERHANG = "Overhang"; + // Nose cone settings public static final String SHAPE = "Shape"; public static final String POWER_LAW = "PowerLaw"; - //public static final String BLUNT_RADIUS = "BluntRadius"; + public static final String BLUNT_RADIUS = "BluntRadius"; private static final Map RASAeroNoseConeShapeMap = new HashMap<>(); + //// Nose cone shapes + private static final String SHAPE_CONICAL = "Conical"; + private static final String SHAPE_TANGENT_OGIVE = "Tangent Ogive"; + private static final String SHAPE_VON_KARMAN_OGIVE = "Von Karman Ogive"; + private static final String SHAPE_POWER_LAW = "Power Law"; + private static final String SHAPE_LVHAACK = "LV-Haack"; + private static final String SHAPE_PARABOLIC = "Parabolic"; + private static final String SHAPE_ELLIPTICAL = "Elliptical"; + // Transition settings public static final String REAR_DIAMETER = "RearDiameter"; @@ -53,6 +76,10 @@ public class RASAeroCommonConstants { public static final String FIN_SWEEP_DISTANCE = "SweepDistance"; public static final String FIN_TIP_CHORD = "TipChord"; public static final String FIN_THICKNESS = "Thickness"; + public static final String FIN_LE_RADIUS = "LERadius"; + public static final String FIN_AIRFOIL_SECTION = "AirfoilSection"; + public static final String FIN_FX1 = "FX1"; + public static final String FIN_FX3 = "FX3"; public static final String AIRFOIL_SECTION = "AirfoilSection"; //// LERadius, FX1 and FX3 not used public static final String CROSS_SECTION_SQUARE = "Square"; @@ -67,6 +94,9 @@ public class RASAeroCommonConstants { public static final String RAIL_GUIDE_DIAMETER = "RailGuideDiameter"; public static final String RAIL_GUIDE_HEIGHT = "RailGuideHeight"; + // Launch shoe settings + public static final String LAUNCH_SHOE_AREA = "LaunchShoeArea"; + // Fin can settings public static final String INSIDE_DIAMETER = "InsideDiameter"; public static final String SHOULDER_LENGTH = "ShoulderLength"; @@ -82,8 +112,20 @@ public class RASAeroCommonConstants { public static final String FINISH_CAST_IRON = "Cast Iron (Very Rough)"; // Booster settings - public static final String BOAT_TAIL_LENGTH = "BoattailLength"; - public static final String BOAT_TAIL_REAR_DIAMETER = "BoattailRearDiameter"; + public static final String BOATTAIL_LENGTH = "BoattailLength"; + public static final String BOATTAIL_REAR_DIAMETER = "BoattailRearDiameter"; + public static final String BOATTAIL_OFFSET = "BoattailOffset"; + public static final String NOZZLE_EXIT_DIAMETER = "NozzleExitDiameter"; + + // RocketDesign settings + public static final String CD = "CD"; + public static final String MODIFIED_BARROWMAN = "ModifiedBarrowman"; + public static final String TURBULENCE = "Turbulence"; + public static final String SUSTAINER_NOZZLE = "SustainerNozzle"; + public static final String BOOSTER1_NOZZLE = "Booster1Nozzle"; + public static final String BOOSTER2_NOZZLE = "Booster2Nozzle"; + public static final String USE_BOOSTER1 = "UseBooster1"; + public static final String USE_BOOSTER2 = "UseBooster2"; // Launch site settings public static final String LAUNCH_SITE = "LaunchSite"; @@ -126,7 +168,7 @@ public class RASAeroCommonConstants { /** * Length conversion from OpenRocket units to RASAero units. RASAero is in inches, OpenRocket in meters. */ - public static final double OPENROCKET_TO_RASAERO_TO_LENGTH = 39.37; + public static final double OPENROCKET_TO_RASAERO_LENGTH = 39.37; /** * Altitude conversion from OpenRocket units to RASAero units. RASAero is in feet, OpenRocket in meters. */ @@ -154,13 +196,13 @@ public class RASAeroCommonConstants { } static { - RASAeroNoseConeShapeMap.put("Conical", Transition.Shape.CONICAL); - RASAeroNoseConeShapeMap.put("Tangent Ogive", Transition.Shape.OGIVE); // = Ogive with shape parameter = 1 - RASAeroNoseConeShapeMap.put("Von Karman Ogive", Transition.Shape.HAACK); // = Haack series with shape parameter = 0 - RASAeroNoseConeShapeMap.put("Power Law", Transition.Shape.POWER); - RASAeroNoseConeShapeMap.put("LV-Haack", Transition.Shape.HAACK); // = Haack series with shape parameter = 1/3 - RASAeroNoseConeShapeMap.put("Parabolic", Transition.Shape.POWER); // = Power law with shape parameter = 1/2 - RASAeroNoseConeShapeMap.put("Elliptical", Transition.Shape.ELLIPSOID); + RASAeroNoseConeShapeMap.put(SHAPE_CONICAL, Transition.Shape.CONICAL); + RASAeroNoseConeShapeMap.put(SHAPE_TANGENT_OGIVE, Transition.Shape.OGIVE); // = Ogive with shape parameter = 1 + RASAeroNoseConeShapeMap.put(SHAPE_VON_KARMAN_OGIVE, Transition.Shape.HAACK); // = Haack series with shape parameter = 0 + RASAeroNoseConeShapeMap.put(SHAPE_POWER_LAW, Transition.Shape.POWER); + RASAeroNoseConeShapeMap.put(SHAPE_LVHAACK, Transition.Shape.HAACK); // = Haack series with shape parameter = 1/3 + RASAeroNoseConeShapeMap.put(SHAPE_PARABOLIC, Transition.Shape.POWER); // = Power law with shape parameter = 1/2 + RASAeroNoseConeShapeMap.put(SHAPE_ELLIPTICAL, Transition.Shape.ELLIPSOID); } /** @@ -168,10 +210,48 @@ public class RASAeroCommonConstants { * @param shape The RASAero shape string. * @return The OpenRocket nose cone shape. */ - public static Transition.Shape getNoseConeShapeFromRASAero(String shape) { + public static Transition.Shape RASAERO_TO_OPENROCKET_SHAPE(String shape) { return RASAeroNoseConeShapeMap.get(shape); } + /** + * Returns the OpenRocket nose cone shape from the RASAero shape string. + * @param shape The RASAero shape string. + * @return The OpenRocket nose cone shape object. + */ + public static NoseConeShapeSettings OPENROCKET_TO_RASAERO_SHAPE(Transition.Shape shape, double shapeParameter) + throws RASAeroExportException { + if (shape.equals(Transition.Shape.CONICAL)) { + return new NoseConeShapeSettings(SHAPE_CONICAL); + } else if (shape.equals(Transition.Shape.OGIVE) && MathUtil.equals(shapeParameter, 1)) { + return new NoseConeShapeSettings(SHAPE_TANGENT_OGIVE); + } else if (shape.equals(Transition.Shape.HAACK) && MathUtil.equals(shapeParameter, 0)) { + return new NoseConeShapeSettings(SHAPE_VON_KARMAN_OGIVE); + } else if (shape.equals(Transition.Shape.POWER) && MathUtil.equals(shapeParameter, 0.5, 0.01)) { + return new NoseConeShapeSettings(SHAPE_PARABOLIC); + } else if (shape.equals(Transition.Shape.POWER)) { + return new NoseConeShapeSettings(SHAPE_POWER_LAW, shapeParameter); + } else if (shape.equals(Transition.Shape.HAACK) && MathUtil.equals(shapeParameter, 0.33, 0.01)) { + return new NoseConeShapeSettings(SHAPE_LVHAACK); + } else if (shape.equals(Transition.Shape.ELLIPSOID)) { + return new NoseConeShapeSettings(SHAPE_ELLIPTICAL); + } + + // Special cases + else if (shape.equals(Transition.Shape.OGIVE)) { + throw new RASAeroExportException( + String.format("RASAero only supports Ogive nose cones with shape parameter 1, not %.2f", shapeParameter)); + } else if (shape.equals(Transition.Shape.HAACK)) { + throw new RASAeroExportException( + String.format("RASAero only supports Haack nose cones with shape parameter 0 or 0.33, not %.2f", shapeParameter)); + } else if (shape.equals(Transition.Shape.PARABOLIC)) { + throw new RASAeroExportException("RASAero does not support Parabolic nose cones"); + } + + throw new RASAeroExportException( + String.format("Invalid shape and shape parameter combination: %s, %.2f", shape.getName(), shapeParameter)); + } + /** * RASAero has a slightly different way of specifying shapes compared to OpenRocket. For instance * RASAero has an "LV-Haack" shape, which is the same as the OpenRocket "Haack" shape with a shape @@ -180,27 +260,27 @@ public class RASAeroCommonConstants { * @param shape The RASAero shape string. * @return The shape parameter for the OpenRocket nose cone. */ - public static double getNoseConeShapeParameterFromRASAeroShape(String shape) { - if ("Conical".equals(shape)) { + public static double RASAERO_TO_OPENROCKET_SHAPE_PARAMETER(String shape) { + if (SHAPE_CONICAL.equals(shape)) { return 0.0; // Not really needed, but just to be explicit - } else if ("Tangent Ogive".equals(shape)) { + } else if (SHAPE_TANGENT_OGIVE.equals(shape)) { return 1.0; - } else if ("Von Karman Ogive".equals(shape)) { + } else if (SHAPE_VON_KARMAN_OGIVE.equals(shape)) { return 0.0; - } else if ("Power Law".equals(shape)) { + } else if (SHAPE_POWER_LAW.equals(shape)) { return 0.0; // Not really needed, but just to be explicit (will be overwritten later) - } else if ("LV-Haack".equals(shape)) { + } else if (SHAPE_LVHAACK.equals(shape)) { return 0.33; - } else if ("Parabolic".equals(shape)) { + } else if (SHAPE_PARABOLIC.equals(shape)) { return 0.5; - } else if ("Elliptical".equals(shape)) { + } else if (SHAPE_ELLIPTICAL.equals(shape)) { return 0.0; // Not really needed, but just to be explicit } else { return 0.0; } } - public static FinSet.CrossSection getFinCrossSectionFromRASAero(String crossSection, WarningSet warnings) { + public static FinSet.CrossSection RASAERO_TO_OPENROCKET_FIN_CROSSSECTION(String crossSection, WarningSet warnings) { if (CROSS_SECTION_SQUARE.equals(crossSection)) { return FinSet.CrossSection.SQUARE; } else if (CROSS_SECTION_ROUNDED.equals(crossSection)) { @@ -213,7 +293,20 @@ public class RASAeroCommonConstants { } } - public static ExternalComponent.Finish getSurfaceFinishFromRASAero(String surfaceFinish, WarningSet warnings) { + public static String OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(FinSet.CrossSection crossSection) { + if (FinSet.CrossSection.SQUARE.equals(crossSection)) { + return CROSS_SECTION_SQUARE; + } else if (FinSet.CrossSection.ROUNDED.equals(crossSection)) { + return CROSS_SECTION_ROUNDED; + } else if (FinSet.CrossSection.AIRFOIL.equals(crossSection)) { + return CROSS_SECTION_SUBSONIC_NACA; + } else { + //TODO: warnings.add("Unknown fin cross section: " + crossSection + "."); + return null; + } + } + + public static ExternalComponent.Finish RASAERO_TO_OPENROCKET_SURFACE(String surfaceFinish, WarningSet warnings) { // NOTE: the RASAero surface finishes are not really the same as the OpenRocket surface finishes. There are some // approximations here. if (FINISH_SMOOTH.equals(surfaceFinish)) { @@ -238,7 +331,29 @@ public class RASAeroCommonConstants { } } - public static DeploymentConfiguration.DeployEvent getDeployEventFromRASAero(String deployEvent, WarningSet warnings) { + public static String OPENROCKET_TO_RASAERO_SURFACE(ExternalComponent.Finish finish) { + if (finish.equals(ExternalComponent.Finish.MIRROR)) { + return FINISH_SMOOTH; + } else if (finish.equals(ExternalComponent.Finish.FINISHPOLISHED)) { + return FINISH_POLISHED; + } else if (finish.equals(ExternalComponent.Finish.OPTIMUM)) { + return FINISH_SHEET_METAL; + } else if (finish.equals(ExternalComponent.Finish.SMOOTH)) { + return FINISH_CAMOUFLAGE; + } else if (finish.equals(ExternalComponent.Finish.NORMAL)) { + return FINISH_ROUGH_CAMOUFLAGE; + } else if (finish.equals(ExternalComponent.Finish.UNFINISHED)) { + return FINISH_GALVANIZED; + } else if (finish.equals(ExternalComponent.Finish.ROUGHUNFINISHED)) { + return FINISH_CAST_IRON; + } else { + // TODO + //warnings.add("Unknown surface finish: " + finish + ", defaulting to Smooth."); + return FINISH_SMOOTH; + } + } + + public static DeploymentConfiguration.DeployEvent RASAERO_TO_OPENROCKET_DEPLOY_EVENT(String deployEvent, WarningSet warnings) { if (DEPLOYMENT_NONE.equals(deployEvent)) { return DeploymentConfiguration.DeployEvent.NEVER; } else if (DEPLOYMENT_APOGEE.equals(deployEvent)) { @@ -249,4 +364,39 @@ public class RASAeroCommonConstants { warnings.add("Unknown deployment event: " + deployEvent + ", defaulting to apogee."); return DeploymentConfiguration.DeployEvent.APOGEE; } + + public static String OPENROCKET_TO_RASAERO_COLOR(Color color) { + if (color != null) { + if (color.equals(Color.BLACK)) { + return "Black"; // Currently the only officially supported color by RASAero + } + } + return "Blue"; // But we can also apply our own color hehe + } + + /** + * Class containing the RASAero nose cone shape and shape parameter settings + */ + public static class NoseConeShapeSettings { + private final String shape; + private final Double shapeParameter; + + public NoseConeShapeSettings(String shape, double shapeParameter) { + this.shape = shape; + this.shapeParameter = shapeParameter; + } + + public NoseConeShapeSettings(String shape) { + this.shape = shape; + this.shapeParameter = null; + } + + public String getShape() { + return shape; + } + + public Double getShapeParameter() { + return shapeParameter; + } + } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java new file mode 100644 index 000000000..42dad3103 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java @@ -0,0 +1,133 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.util.MathUtil; + +/** + * The base class for most RASAero components. + */ +@XmlRootElement +@XmlType(name="RASAeroBasePartDTO") +@XmlAccessorType(XmlAccessType.FIELD) +public class BasePartDTO { + @XmlElement(name = RASAeroCommonConstants.PART_TYPE) + private String partType; + @XmlElement(name = RASAeroCommonConstants.LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double length; + @XmlElement(name = RASAeroCommonConstants.DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double diameter; + @XmlElement(name = RASAeroCommonConstants.LOCATION) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double location; + @XmlElement(name = RASAeroCommonConstants.COLOR) + private String color; + + @XmlTransient + private final RocketComponent component; + + /** + * We need a default no-args constructor. + */ + public BasePartDTO() { + this.component = null; + } + + protected BasePartDTO(RocketComponent component) throws RASAeroExportException { + this.component = component; + + if (component instanceof BodyTube) { + setPartType(RASAeroCommonConstants.BODY_TUBE); + setDiameter(((BodyTube) component).getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else if (component instanceof NoseCone) { + setPartType(RASAeroCommonConstants.NOSE_CONE); + NoseCone noseCone = (NoseCone) component; + if (noseCone.isFlipped()) { + throw new RASAeroExportException("Nose cone may not be flipped"); + } + setDiameter(((NoseCone) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else if (component instanceof Transition) { + setPartType(RASAeroCommonConstants.TRANSITION); + // This is a bit strange: I would expect diameter to be the fore radius, since you also have a rearDiamter + // field for transitions, but okay + setDiameter(((Transition) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else if (component instanceof AxialStage) { + setPartType(RASAeroCommonConstants.BOOSTER); + AxialStage stage = (AxialStage) component; + if (stage.getChildCount() == 0 || !(stage.getChild(0) instanceof BodyTube)) { + throw new RASAeroExportException("First component of booster must be body tube"); + } + setDiameter(stage.getBoundingRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else { + throw new RASAeroExportException("Unsupported component: " + component.getComponentName()); + } + + setLength(component.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setLocation(component.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(component.getColor())); + } + + public String getPartType() { + return partType; + } + + public void setPartType(String partType) { + this.partType = partType; + } + + public Double getLength() { + return length; + } + + public void setLength(Double length) throws RASAeroExportException { + if (MathUtil.equals(length, 0)) { + throw new RASAeroExportException(String.format("Length of '%s' must be greater than 0", component.getName())); + } + this.length = length; + } + + public Double getDiameter() { + return diameter; + } + + public void setDiameter(Double diameter) throws RASAeroExportException { + if (MathUtil.equals(diameter, 0)) { + throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0", component.getName())); + } + this.diameter = diameter; + } + + public Double getLocation() { + return location; + } + + public void setLocation(Double location) { + this.location = location; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java new file mode 100644 index 000000000..a12f7f42b --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java @@ -0,0 +1,156 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.BodyTube; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.util.MathUtil; + +@XmlRootElement(name = RASAeroCommonConstants.BODY_TUBE) +@XmlAccessorType(XmlAccessType.FIELD) +public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { + @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchLugDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchLugLength = 0d; + @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double railGuideDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_HEIGHT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double railGuideHeight = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_SHOE_AREA) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchShoeArea = 0d; // Currently not available in OR + + @XmlElement(name = RASAeroCommonConstants.BOATTAIL_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double boattailLength = 0d; + @XmlElement(name = RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double boattailRearDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.BOATTAIL_OFFSET) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double boattailOffset = 0d; + @XmlElement(name = RASAeroCommonConstants.OVERHANG) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double overhang = 0d; + + @XmlElementRef(name = RASAeroCommonConstants.FIN, type = FinDTO.class) + private FinDTO fin; + + /** + * We need a default no-args constructor. + */ + public BodyTubeDTO() { } + + public BodyTubeDTO(BodyTube bodyTube) throws RASAeroExportException { + super(bodyTube); + applyBodyTubeSettings(bodyTube); + } + + public Double getLaunchLugDiameter() { + return launchLugDiameter; + } + + public void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException { + if (MathUtil.equals(launchLugDiameter, 0)) { + throw new RASAeroExportException("Launch lug diameter can not be 0"); + } + this.launchLugDiameter = launchLugDiameter; + } + + public Double getLaunchLugLength() { + return launchLugLength; + } + + public void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException { + if (MathUtil.equals(launchLugLength, 0)) { + throw new RASAeroExportException("Launch lug length can not be 0"); + } + this.launchLugLength = launchLugLength; + } + + public Double getRailGuideDiameter() { + return railGuideDiameter; + } + + public void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException { + if (MathUtil.equals(railGuideDiameter, 0)) { + throw new RASAeroExportException("Rail button diameter can not be 0"); + } + this.railGuideDiameter = railGuideDiameter; + } + + public Double getRailGuideHeight() { + return railGuideHeight; + } + + public void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException { + if (MathUtil.equals(railGuideHeight, 0)) { + throw new RASAeroExportException("Rail button height can not be 0"); + } + this.railGuideHeight = railGuideHeight; + } + + public Double getLaunchShoeArea() { + return launchShoeArea; + } + + public void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException { + if (MathUtil.equals(launchShoeArea, 0)) { + throw new RASAeroExportException("Launch shoe area can not be 0"); + } + this.launchShoeArea = launchShoeArea; + } + + public Double getBoattailLength() { + return boattailLength; + } + + public void setBoattailLength(Double boattailLength) { + this.boattailLength = boattailLength; + } + + public Double getBoattailRearDiameter() { + return boattailRearDiameter; + } + + public void setBoattailRearDiameter(Double boattailRearDiameter) { + this.boattailRearDiameter = boattailRearDiameter; + } + + public Double getBoattailOffset() { + return boattailOffset; + } + + public void setBoattailOffset(Double boattailOffset) { + this.boattailOffset = boattailOffset; + } + + public Double getOverhang() { + return overhang; + } + + public void setOverhang(Double overhang) { + this.overhang = overhang; + } + + public FinDTO getFin() { + return fin; + } + + public void setFin(FinDTO fin) { + this.fin = fin; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java new file mode 100644 index 000000000..3c7d84e41 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -0,0 +1,90 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.RailButton; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; + +public interface BodyTubeDTOAdapter { + default void applyBodyTubeSettings(BodyTube bodyTube) throws RASAeroExportException { + for (RocketComponent child : bodyTube.getChildren()) { + if (child instanceof TrapezoidFinSet) { + setFin(new FinDTO((TrapezoidFinSet) child)); + } else if (child instanceof LaunchLug) { + if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe + //TODO: warning.add(String.format("Already added a rail button, ignoring launch lug '%s'", child.getName()); + continue; + } + if (!MathUtil.equals(getLaunchShoeArea(), 0)) { + //TODO: warning.add(String.format("Already added a launch shoe, ignoring launch lug '%s'", child.getName()); + continue; + } + + LaunchLug lug = (LaunchLug) child; + setLaunchLugDiameter(lug.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + if (lug.getInstanceCount() == 2) { + setLaunchLugLength(lug.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else { + //TODO:warnings.add(String.format("Instance count of '%s' not set to 2, defaulting to 2 and adjusting + // launch lug length accordingly.", lug.getName()") + setLaunchLugLength(lug.getLength() * lug.getInstanceCount() / 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + } else if (child instanceof RailButton) { + if (!MathUtil.equals(getLaunchLugDiameter(), 0) || !MathUtil.equals(getLaunchLugLength(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe + // TODO: warning.add(String.format("Already added a launch lug, ignoring rail button '%s'", child.getName())); + continue; + } + if (!MathUtil.equals(getLaunchShoeArea(), 0)) { + // TODO: warning.add(String.format("Already added a launch shoe, ignoring rail button '%s'", child.getName())); + continue; + } + + RailButton button = (RailButton) child; + setRailGuideDiameter(button.getOuterDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setRailGuideHeight(button.getTotalHeight() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + + if (button.getInstanceCount() != 2) { + //TODO: warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2", button.getName(), button.getInstanceCount())); + } + } else { + // TODO: warnings.add(String.format("Unsupported component '%s', ignoring.", child.getComponentName())); + } + } + } + + Double getLaunchLugDiameter(); + + void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException; + + Double getLaunchLugLength(); + + void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException; + + Double getRailGuideDiameter(); + + void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException; + + Double getRailGuideHeight(); + + void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException; + + Double getLaunchShoeArea(); + + void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException; + + Double getBoattailLength(); + + void setBoattailLength(Double boattailLength); + + Double getBoattailRearDiameter(); + + void setBoattailRearDiameter(Double boattailRearDiameter); + + FinDTO getFin(); + + void setFin(FinDTO fin); +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java new file mode 100644 index 000000000..b606c4e76 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -0,0 +1,285 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.util.MathUtil; + +@XmlRootElement(name = RASAeroCommonConstants.BOOSTER) +@XmlAccessorType(XmlAccessType.FIELD) +public class BoosterDTO implements BodyTubeDTOAdapter { + + @XmlElement(name = RASAeroCommonConstants.PART_TYPE) + private String partType; + @XmlElement(name = RASAeroCommonConstants.LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double length; + @XmlElement(name = RASAeroCommonConstants.DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double diameter; + @XmlElement(name = RASAeroCommonConstants.INSIDE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double insideDiameter; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchLugDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchLugLength = 0d; + @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double railGuideDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_HEIGHT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double railGuideHeight = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_SHOE_AREA) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double launchShoeArea = 0d; // Currently not available in OR + @XmlElement(name = RASAeroCommonConstants.LOCATION) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double location; + @XmlElement(name = RASAeroCommonConstants.COLOR) + private String color; + @XmlElement(name = RASAeroCommonConstants.SHOULDER_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double shoulderLength; + @XmlElement(name = RASAeroCommonConstants.NOZZLE_EXIT_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double nozzleExitDiameter; + @XmlElement(name = RASAeroCommonConstants.BOATTAIL_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double boattailLength; + @XmlElement(name = RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double boattailRearDiameter; + + @XmlElementRef(name = RASAeroCommonConstants.FIN, type = FinDTO.class) + private FinDTO fin; + + + /** + * We need a default, no-args constructor. + */ + public BoosterDTO() { } + + protected BoosterDTO(Rocket rocket, AxialStage stage) throws RASAeroExportException { + int stageNr = rocket.getChildPosition(stage); // Use this instead of stage.getStageNumber() in case there are parallel stages in the design + if (stageNr != 1 && stageNr != 2) { + throw new RASAeroExportException(String.format("Invalid stage number '%d' for booster stage '%s'", stageNr, stage.getName())); + } + + if (stage.getChildCount() == 0) { + throw new RASAeroExportException(String.format("Stage '%s' can not be empty", stage.getName())); + } + + RocketComponent firstChild = stage.getChild(0); + if (!(firstChild instanceof BodyTube) && + !(firstChild instanceof Transition && !(firstChild instanceof NoseCone))) { + throw new RASAeroExportException(String.format("First component of stage '%s' must be a body tube or transition", stage.getName())); + } + final BodyTube firstTube; + if (firstChild instanceof Transition) { + if (stage.getChildCount() == 1 || !(stage.getChild(1) instanceof BodyTube)) { + throw new RASAeroExportException( + String.format("When the first component of stage '%s' is a transition, the second one must be a body tube", + stage.getName())); + } + + Transition transition = (Transition) firstChild; + SymmetricComponent previousComponent = transition.getPreviousSymmetricComponent(); + if (previousComponent == null) { + throw new RASAeroExportException(String.format("No previous component for '%s' in stage '%s'", + firstChild.getName(), stage.getName())); + } + + if (!MathUtil.equals(transition.getForeRadius(), previousComponent.getAftRadius())) { + throw new RASAeroExportException( + String.format("Transition '%s' in stage '%s' must have the same fore radius as the aft radius of its previous component '%s'", + transition.getName(), stage.getName(), previousComponent.getName())); + } + + firstTube = (BodyTube) stage.getChild(1); + if (!MathUtil.equals(firstTube.getOuterRadius(), transition.getAftRadius())) { + throw new RASAeroExportException( + String.format("Radius of '%s' in stage '%s' must be the same as the aft radius of '%s", + firstTube.getName(), stage.getName(), transition.getName())); + } + + setShoulderLength(firstChild.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setInsideDiameter(transition.getForeRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } else { + firstTube = (BodyTube) stage.getChild(0); + } + + applyBodyTubeSettings(firstTube); + + TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); + if (finSet == null) { + throw new RASAeroExportException( + String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet", + firstTube.getName(), stage.getName())); + } + setFin(new FinDTO(finSet)); + + setPartType(RASAeroCommonConstants.BOOSTER); + setLength(firstTube.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setLocation(firstChild.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(firstTube.getColor())); + } + + private TrapezoidFinSet getFinSetFromBodyTube(BodyTube bodyTube) { + for (RocketComponent child : bodyTube.getChildren()) { + if (child instanceof TrapezoidFinSet) { + return (TrapezoidFinSet) child; + } + } + return null; + } + + public String getPartType() { + return partType; + } + + public void setPartType(String partType) { + this.partType = partType; + } + + public Double getLength() { + return length; + } + + public void setLength(Double length) { + this.length = length; + } + + public Double getDiameter() { + return diameter; + } + + public void setDiameter(Double diameter) { + this.diameter = diameter; + } + + public Double getInsideDiameter() { + return insideDiameter; + } + + public void setInsideDiameter(Double insideDiameter) { + this.insideDiameter = insideDiameter; + } + + public Double getLaunchLugDiameter() { + return launchLugDiameter; + } + + public void setLaunchLugDiameter(Double launchLugDiameter) { + this.launchLugDiameter = launchLugDiameter; + } + + public Double getLaunchLugLength() { + return launchLugLength; + } + + public void setLaunchLugLength(Double launchLugLength) { + this.launchLugLength = launchLugLength; + } + + public Double getRailGuideDiameter() { + return railGuideDiameter; + } + + public void setRailGuideDiameter(Double railGuideDiameter) { + this.railGuideDiameter = railGuideDiameter; + } + + public Double getRailGuideHeight() { + return railGuideHeight; + } + + public void setRailGuideHeight(Double railGuideHeight) { + this.railGuideHeight = railGuideHeight; + } + + public Double getLaunchShoeArea() { + return launchShoeArea; + } + + public void setLaunchShoeArea(Double launchShoeArea) { + this.launchShoeArea = launchShoeArea; + } + + public Double getLocation() { + return location; + } + + public void setLocation(Double location) { + this.location = location; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public Double getShoulderLength() { + return shoulderLength; + } + + public void setShoulderLength(Double shoulderLength) { + this.shoulderLength = shoulderLength; + } + + public Double getNozzleExitDiameter() { + return nozzleExitDiameter; + } + + public void setNozzleExitDiameter(Double nozzleExitDiameter) { + this.nozzleExitDiameter = nozzleExitDiameter; + } + + public Double getBoattailLength() { + return boattailLength; + } + + public void setBoattailLength(Double boattailLength) { + this.boattailLength = boattailLength; + } + + public Double getBoattailRearDiameter() { + return boattailRearDiameter; + } + + public void setBoattailRearDiameter(Double boattailRearDiameter) { + this.boattailRearDiameter = boattailRearDiameter; + } + + public FinDTO getFin() { + return fin; + } + + public void setFin(FinDTO fin) { + this.fin = fin; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java new file mode 100644 index 000000000..947f4a1e2 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java @@ -0,0 +1,160 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlRootElement(name = RASAeroCommonConstants.FIN) +@XmlAccessorType(XmlAccessType.FIELD) +public class FinDTO { + @XmlElement(name = RASAeroCommonConstants.FIN_COUNT) + private int count; + @XmlElement(name = RASAeroCommonConstants.FIN_CHORD) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double chord; + @XmlElement(name = RASAeroCommonConstants.FIN_SPAN) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double span; + @XmlElement(name = RASAeroCommonConstants.FIN_SWEEP_DISTANCE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sweepDistance; + @XmlElement(name = RASAeroCommonConstants.FIN_TIP_CHORD) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double tipChord; + @XmlElement(name = RASAeroCommonConstants.FIN_THICKNESS) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double thickness; + @XmlElement(name = RASAeroCommonConstants.FIN_LE_RADIUS) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double LERadius = 0d; // Leading edge radius + @XmlElement(name = RASAeroCommonConstants.LOCATION) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double location; + @XmlElement(name = RASAeroCommonConstants.AIRFOIL_SECTION) + private String airfoilSection; + @XmlElement(name = RASAeroCommonConstants.FIN_FX1) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double FX1 = 0d; + @XmlElement(name = RASAeroCommonConstants.FIN_FX3) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double FX3 = 0d; + + /** + * We need a default no-args constructor. + */ + public FinDTO() { + } + + public FinDTO(TrapezoidFinSet fin) throws RASAeroExportException { + int finCount = fin.getFinCount(); + if (finCount < 3 || finCount > 8) { + throw new RASAeroExportException( + String.format("Fin set '%s' must have a fin count between 3 and 8", fin.getName())); + } + + setCount(fin.getFinCount()); + setChord(fin.getRootChord() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setTipChord(fin.getTipChord() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setSpan(fin.getSpan() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setSweepDistance(fin.getSweep() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setThickness(fin.getThickness() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setAirfoilSection(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(fin.getCrossSection())); + setLocation((-fin.getAxialOffset(AxialMethod.BOTTOM) + fin.getLength()) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public Double getChord() { + return chord; + } + + public void setChord(Double chord) { + this.chord = chord; + } + + public Double getSpan() { + return span; + } + + public void setSpan(Double span) { + this.span = span; + } + + public Double getSweepDistance() { + return sweepDistance; + } + + public void setSweepDistance(Double sweepDistance) { + this.sweepDistance = sweepDistance; + } + + public Double getTipChord() { + return tipChord; + } + + public void setTipChord(Double tipChord) { + this.tipChord = tipChord; + } + + public Double getThickness() { + return thickness; + } + + public void setThickness(Double thickness) { + this.thickness = thickness; + } + + public Double getLERadius() { + return LERadius; + } + + public void setLERadius(Double LERadius) { + this.LERadius = LERadius; + } + + public Double getLocation() { + return location; + } + + public void setLocation(Double location) { + this.location = location; + } + + public String getAirfoilSection() { + return airfoilSection; + } + + public void setAirfoilSection(String airfoilSection) { + this.airfoilSection = airfoilSection; + } + + public Double getFX1() { + return FX1; + } + + public void setFX1(Double FX1) { + this.FX1 = FX1; + } + + public Double getFX3() { + return FX3; + } + + public void setFX3(Double FX3) { + this.FX3 = FX3; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java new file mode 100644 index 000000000..7c8fc83fe --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java @@ -0,0 +1,4 @@ +package net.sf.openrocket.file.rasaero.export; + +public class LaunchSiteDTO { +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java new file mode 100644 index 000000000..c1fae0c47 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java @@ -0,0 +1,70 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.NoseCone; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants.NoseConeShapeSettings; + +@XmlRootElement(name = RASAeroCommonConstants.NOSE_CONE) +@XmlAccessorType(XmlAccessType.FIELD) +public class NoseConeDTO extends BasePartDTO { + + @XmlElement(name = RASAeroCommonConstants.SHAPE) + private String shape; + @XmlElement(name = RASAeroCommonConstants.BLUNT_RADIUS) + private double bluntRadius = 0; + @XmlElement(name = RASAeroCommonConstants.POWER_LAW) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double powerLaw; + + /** + * We need a default no-args constructor. + */ + public NoseConeDTO() { + } + + public NoseConeDTO(NoseCone noseCone) throws RASAeroExportException { + super(noseCone); + + NoseConeShapeSettings shapeSettings = + RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SHAPE(noseCone.getShapeType(), noseCone.getShapeParameter()); + + setShape(shapeSettings.getShape()); + Double shapeParameter = shapeSettings.getShapeParameter(); + if (shapeParameter != null) { + setPowerLaw(shapeParameter); + } + } + + public String getShape() { + return shape; + } + + public void setShape(String shape) { + this.shape = shape; + } + + public Double getPowerLaw() { + return powerLaw; + } + + public void setPowerLaw(Double powerLaw) { + this.powerLaw = powerLaw; + } + + public double getBluntRadius() { + return bluntRadius; + } + + public void setBluntRadius(double bluntRadius) { + this.bluntRadius = bluntRadius; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java new file mode 100644 index 000000000..fc5ac0051 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java @@ -0,0 +1,87 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * The top level RASAero document. + */ +@XmlRootElement(name = RASAeroCommonConstants.RASAERO_DOCUMENT) +@XmlAccessorType(XmlAccessType.FIELD) +public class RASAeroDocumentDTO { + @XmlElement(name = RASAeroCommonConstants.FILE_VERSION) + private final String version = "2"; + + @XmlElement(name = RASAeroCommonConstants.ROCKET_DESIGN) + private RocketDesignDTO design; + + @XmlElement(name = RASAeroCommonConstants.LAUNCH_SITE) + private LaunchSiteDTO launchSite; + + @XmlElement(name = RASAeroCommonConstants.RECOVERY) + private RecoveryDTO recovery; + + + @XmlElement(name = RASAeroCommonConstants.MACH_ALT) + private String machAlt = ""; // Currently not implemented + + /* + @XmlElementWrapper(name=RASAeroCommonConstants.SIMULATION_LIST) + @XmlElement(name=RASAeroCommonConstants.SIMULATION) + */ + @XmlElement(name = RASAeroCommonConstants.SIMULATION_LIST) + private SimulationListDTO simulationList = null; + + /** + * Get the subordinate design DTO. + * + * @return the RocketDesignDTO + */ + public RocketDesignDTO getDesign() { + return design; + } + + public void setDesign(RocketDesignDTO theDesign) { + this.design = theDesign; + } + + public LaunchSiteDTO getLaunchSite() { + return launchSite; + } + + public void setLaunchSite(LaunchSiteDTO launchSite) { + this.launchSite = launchSite; + } + + public RecoveryDTO getRecovery() { + return recovery; + } + + public void setRecovery(RecoveryDTO recovery) { + this.recovery = recovery; + } + + public SimulationListDTO getSimulationList() { + return simulationList; + } + + public void setSimulationList(SimulationListDTO simulationList) { + this.simulationList = simulationList; + } + + public String getMachAlt() { + return this.machAlt; + } + + public void setMachAlt(String machAlt) { + this.machAlt = machAlt; + } + + public String getVersion() { + return version; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java new file mode 100644 index 000000000..45a717611 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -0,0 +1,93 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.StorageOptions; +import net.sf.openrocket.file.RocketSaver; +import net.sf.openrocket.rocketcomponent.Rocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +public class RASAeroSaver extends RocketSaver { + /** + * The logger. + */ + private static final Logger log = LoggerFactory.getLogger(RASAeroSaver.class); + + public static class RASAeroExportException extends Exception { + public RASAeroExportException(String errorMessage) { + super(errorMessage); + } + } + + /** + * This method marshals an OpenRocketDocument (OR design) to RASAero-compliant XML. + * + * @param doc the OR design + * @return RASAero-compliant XML + */ + public String marshalToRASAero(OpenRocketDocument doc) { + try { + JAXBContext binder = JAXBContext.newInstance(RASAeroDocumentDTO.class); + Marshaller marshaller = binder.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + StringWriter sw = new StringWriter(); + + marshaller.marshal(toRASAeroDocumentDTO(doc), sw); + return sw.toString(); + } catch (RASAeroExportException e) { + throw new RuntimeException(e); + } catch (Exception e) { + log.error("Could not marshall a design to RASAero format. " + e.getMessage()); + } + + return null; + } + + @Override + public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException { + log.info("Saving .CDX1 file"); + + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, StandardCharsets.UTF_8)); + writer.write(marshalToRASAero(doc)); + writer.flush(); + } + + @Override + public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) { + return marshalToRASAero(doc).length(); + } + + /** + * Root conversion method. It iterates over all subcomponents. + * + * @param doc the OR design + * @return a corresponding RASAero representation + */ + private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc) throws RASAeroExportException { + RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); + rad.setDesign(toRocketDesignDTO(doc.getRocket())); + + return rad; + } + + /** + * Create the RASAero rocket design (containing all the actual rocket components). + * @param rocket the OR rocket to export the components from + * @return the RASAero rocket design + */ + private RocketDesignDTO toRocketDesignDTO(Rocket rocket) throws RASAeroExportException { + RocketDesignDTO result = new RocketDesignDTO(rocket); + return result; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java new file mode 100644 index 000000000..8cbb8233f --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java @@ -0,0 +1,4 @@ +package net.sf.openrocket.file.rasaero.export; + +public class RecoveryDTO { +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java new file mode 100644 index 000000000..4453018e9 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -0,0 +1,202 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.ArrayList; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.List; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; + +@XmlAccessorType(XmlAccessType.FIELD) +public class RocketDesignDTO { + @XmlElementRefs({ + @XmlElementRef(name = RASAeroCommonConstants.BODY_TUBE, type = BodyTubeDTO.class), + @XmlElementRef(name = RASAeroCommonConstants.NOSE_CONE, type = NoseConeDTO.class), + @XmlElementRef(name = RASAeroCommonConstants.TRANSITION, type = TransitionDTO.class), + @XmlElementRef(name = RASAeroCommonConstants.BOOSTER, type = BoosterDTO.class) + }) + private final List externalPart = new ArrayList<>(); + + @XmlElementRefs({ + @XmlElementRef(name = RASAeroCommonConstants.BOOSTER, type = BoosterDTO.class), + }) + private final List boosters = new ArrayList<>(); + + @XmlElement(name = RASAeroCommonConstants.SURFACE_FINISH) + private String surface = RASAeroCommonConstants.FINISH_SMOOTH; + @XmlElement(name = RASAeroCommonConstants.CD) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double CD = 0d; + @XmlElement(name = RASAeroCommonConstants.MODIFIED_BARROWMAN) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean modifiedBarrowman = false; + @XmlElement(name = RASAeroCommonConstants.TURBULENCE) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean turbulence = false; + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_NOZZLE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sustainerNozzle = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_NOZZLE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1Nozzle = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_NOZZLE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster2Nozzle = 0d; + @XmlElement(name = RASAeroCommonConstants.USE_BOOSTER1) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean useBooster1 = false; + @XmlElement(name = RASAeroCommonConstants.USE_BOOSTER2) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean useBooster2 = false; + @XmlElement(name = RASAeroCommonConstants.COMMENTS) + private String comments = ""; + + public RocketDesignDTO(Rocket rocket) throws RASAeroExportException { + setComments(rocket.getComment()); + if (rocket.getChildCount() > 3) { + throw new RASAeroExportException("Rocket should have no more then 3 stages (excl. boosters) in total"); + } + setUseBooster1(rocket.getStageCount() >= 2); + setUseBooster2(rocket.getStageCount() == 3); + + AxialStage sustainer = rocket.getStage(0); + + // Export components from sustainer + for (int i = 0; i < sustainer.getChildCount(); i++) { + RocketComponent component = sustainer.getChild(i); + if (i == 0 && !(component instanceof NoseCone)) { + throw new RASAeroExportException("First component of the sustainer must be a nose cone"); + } else if (i == 1 && !(component instanceof BodyTube)) { + throw new RASAeroExportException("Second component of the sustainer must be a body tube"); + } + if (component instanceof BodyTube) { + addExternalPart(new BodyTubeDTO((BodyTube) component)); + } else if (component instanceof NoseCone) { + if (i != 0) { + throw new RASAeroExportException("A nose cone can only be the first component of the rocket"); + } + addExternalPart(new NoseConeDTO((NoseCone) component)); + // Set the global surface finish to that of the first nose cone + setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish())); + } + else if (component instanceof Transition) { + addExternalPart(new TransitionDTO((Transition) component)); + } + } + + // Export components from other stages + for (int i = 1; i < rocket.getChildCount(); i++) { + addBooster(new BoosterDTO(rocket, (AxialStage) rocket.getChild(i))); + } + } + + public String getSurface() { + return surface; + } + + public void setSurface(String surface) { + this.surface = surface; + } + + public double getCD() { + return CD; + } + + public void setCD(double CD) { + this.CD = CD; + } + + public boolean isModifiedBarrowman() { + return modifiedBarrowman; + } + + public void setModifiedBarrowman(boolean modifiedBarrowman) { + this.modifiedBarrowman = modifiedBarrowman; + } + + public Boolean isTurbulence() { + return turbulence; + } + + public void setTurbulence(Boolean turbulence) { + this.turbulence = turbulence; + } + + public Double getSustainerNozzle() { + return sustainerNozzle; + } + + public void setSustainerNozzle(Double sustainerNozzle) { + this.sustainerNozzle = sustainerNozzle; + } + + public Double getBooster1Nozzle() { + return booster1Nozzle; + } + + public void setBooster1Nozzle(Double booster1Nozzle) { + this.booster1Nozzle = booster1Nozzle; + } + + public Double getBooster2Nozzle() { + return booster2Nozzle; + } + + public void setBooster2Nozzle(Double booster2Nozzle) { + this.booster2Nozzle = booster2Nozzle; + } + + public Boolean isUseBooster1() { + return useBooster1; + } + + public void setUseBooster1(Boolean useBooster1) { + this.useBooster1 = useBooster1; + } + + public Boolean isUseBooster2() { + return useBooster2; + } + + public void setUseBooster2(Boolean useBooster2) { + this.useBooster2 = useBooster2; + } + + public String getComments() { + return comments; + } + + public void setComments(String comments) { + this.comments = comments; + } + + public List getExternalPart() { + return externalPart; + } + + public void addExternalPart(BasePartDTO theExternalPartDTO) { + externalPart.add(theExternalPartDTO); + } + + public List getBoosters() { + return boosters; + } + + public void addBooster(BoosterDTO boosterDTO) { + boosters.add(boosterDTO); + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java new file mode 100644 index 000000000..a3af9c971 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -0,0 +1,4 @@ +package net.sf.openrocket.file.rasaero.export; + +public class SimulationListDTO { +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java new file mode 100644 index 000000000..6259876cb --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -0,0 +1,61 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.rocketcomponent.RocketComponent; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.rocketcomponent.SymmetricComponent; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.util.MathUtil; + +import java.util.Objects; + +@XmlRootElement(name = RASAeroCommonConstants.TRANSITION) +@XmlAccessorType(XmlAccessType.FIELD) +public class TransitionDTO extends BasePartDTO { + + @XmlElement(name = RASAeroCommonConstants.REAR_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double rearDiameter; + + /** + * We need a default no-args constructor. + */ + public TransitionDTO() { + } + + public TransitionDTO(Transition transition) throws RASAeroExportException { + super(transition); + + if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { + throw new RASAeroExportException("RASAero only supports conical transitions"); + } + + SymmetricComponent previousComp = transition.getPreviousSymmetricComponent(); + if (previousComp == null) { + throw new RASAeroExportException(String.format("Transition '%s' has no previous component", transition.getName())); + } + if (!MathUtil.equals(transition.getForeRadius(), previousComp.getAftRadius())) { + throw new RASAeroExportException( + String.format("Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not (%f)", + transition.getName(), previousComp.getAftRadius(), transition.getForeRadius())); + } + + setRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + + public Double getRearDiameter() { + return rearDiameter; + } + + public void setRearDiameter(Double rearDiameter) { + this.rearDiameter = rearDiameter; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java index cde34acdb..c09f69003 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.rocketcomponent.RocketComponent; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java index 72f1ed3a9..60c688dde 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.PodSet; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java index 44ea54ac0..b5f080418 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.BodyTube; @@ -75,8 +76,8 @@ public class BodyTubeHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.bodyTube.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); - this.bodyTube.setOuterRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); // Not really useful, but included for completeness + this.bodyTube.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + this.bodyTube.setOuterRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); // Not really useful, but included for completeness this.bodyTube.setOuterRadiusAutomatic(true); this.bodyTube.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java index 9b27a7c55..b8cb625d2 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -40,7 +41,7 @@ public class BoosterHandler extends BodyTubeHandler { @Override public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) throws SAXException { - if (RASAeroCommonConstants.BOAT_TAIL_LENGTH.equals(element) || RASAeroCommonConstants.BOAT_TAIL_REAR_DIAMETER.equals(element) + if (RASAeroCommonConstants.BOATTAIL_LENGTH.equals(element) || RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER.equals(element) || RASAeroCommonConstants.SHOULDER_LENGTH.equals(element)) { return PlainTextHandler.INSTANCE; } @@ -50,12 +51,12 @@ public class BoosterHandler extends BodyTubeHandler { @Override public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.closeElement(element, attributes, content, warnings); - if (RASAeroCommonConstants.BOAT_TAIL_LENGTH.equals(element)) { - this.boatTailLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; - } else if (RASAeroCommonConstants.BOAT_TAIL_REAR_DIAMETER.equals(element)) { - this.boatTailRearDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + if (RASAeroCommonConstants.BOATTAIL_LENGTH.equals(element)) { + this.boatTailLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; + } else if (RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER.equals(element)) { + this.boatTailRearDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } else if (RASAeroCommonConstants.SHOULDER_LENGTH.equals(element)) { - this.shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + this.shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java index 353f1a3b6..f1d9699d9 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -59,9 +60,9 @@ public class FinCanHandler extends BodyTubeHandler { super.closeElement(element, attributes, content, warnings); try { if (RASAeroCommonConstants.INSIDE_DIAMETER.equals(element)) { - insideDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + insideDiameter = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } else if (RASAeroCommonConstants.SHOULDER_LENGTH.equals(element)) { - shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + shoulderLength = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java index a0aca4fcb..b650846aa 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; @@ -46,21 +47,21 @@ public class FinHandler extends AbstractElementHandler { if (RASAeroCommonConstants.FIN_COUNT.equals(element)) { finSet.setFinCount(Integer.parseInt(content)); } else if (RASAeroCommonConstants.FIN_CHORD.equals(element)) { - finSet.setRootChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + finSet.setRootChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (RASAeroCommonConstants.FIN_SPAN.equals(element)) { - finSet.setHeight(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + finSet.setHeight(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (RASAeroCommonConstants.FIN_SWEEP_DISTANCE.equals(element)) { - finSet.setSweep(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + finSet.setSweep(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (RASAeroCommonConstants.FIN_TIP_CHORD.equals(element)) { - finSet.setTipChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + finSet.setTipChord(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (RASAeroCommonConstants.FIN_THICKNESS.equals(element)) { - finSet.setThickness(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + finSet.setThickness(Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (RASAeroCommonConstants.AIRFOIL_SECTION.equals(element)) { - finSet.setCrossSection(RASAeroCommonConstants.getFinCrossSectionFromRASAero(content, warnings)); + finSet.setCrossSection(RASAeroCommonConstants.RASAERO_TO_OPENROCKET_FIN_CROSSSECTION(content, warnings)); } else if (RASAeroCommonConstants.LOCATION.equals(element)) { // Location is the location of the front of the fin relative to the bottom of the body tube finSet.setAxialMethod(AxialMethod.BOTTOM); - double location = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + double location = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; location = -location + finSet.getLength(); finSet.setAxialOffset(location); } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java index 66713f85d..a6a001c50 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchLugHandler.java @@ -1,5 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.position.AxialMethod; @@ -14,8 +15,8 @@ import net.sf.openrocket.rocketcomponent.position.AxialMethod; */ public abstract class LaunchLugHandler { public static void addLaunchLug(BodyTube parent, double diameter, double length) { - diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; - length = length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; + length = length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; LaunchLug lug = generateLaunchLugFromRASAeroRailGuide(diameter, length, parent.getLength()); parent.addChild(lug); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java index d8145e260..81c856c3b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java index 84114e199..f8ffb19c4 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.NoseCone; @@ -47,8 +48,8 @@ public class NoseConeHandler extends BaseHandler { public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.closeElement(element, attributes, content, warnings); if (RASAeroCommonConstants.SHAPE.equals(element)) { - this.noseCone.setShapeType(RASAeroCommonConstants.getNoseConeShapeFromRASAero(content)); - this.noseCone.setShapeParameter(RASAeroCommonConstants.getNoseConeShapeParameterFromRASAeroShape(content)); + this.noseCone.setShapeType(RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SHAPE(content)); + this.noseCone.setShapeParameter(RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SHAPE_PARAMETER(content)); } try { if (RASAeroCommonConstants.POWER_LAW.equals(element)) { @@ -63,8 +64,8 @@ public class NoseConeHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.noseCone.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); - this.noseCone.setBaseRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + this.noseCone.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + this.noseCone.setBaseRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); this.noseCone.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java index 3b85c9d5b..bc4a9a8cc 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java @@ -3,6 +3,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java index 6b7217174..2ba8ba2d3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RailGuideHandler.java @@ -1,5 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.position.AxialMethod; @@ -22,8 +23,8 @@ public abstract class RailGuideHandler { * @param height total height of the rail guide, plus the screw height */ public static void addRailGuide(BodyTube parent, double diameter, double height) { - diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; - height = height / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH; + diameter = diameter / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; + height = height / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; RailButton button = generateRailButtonFromRASAeroRailGuide(diameter, height, parent.getLength()); parent.addChild(button); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java index 39f6a9768..ed346a3bd 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; @@ -163,7 +164,7 @@ public class RecoveryHandler extends AbstractElementHandler { recoveryDevice.setName("Recovery Event " + (recoveryDeviceNr+1)); DeploymentConfiguration config = recoveryDevice.getDeploymentConfigurations().getDefault(); - recoveryDevice.setDiameter(size / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + recoveryDevice.setDiameter(size / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); recoveryDevice.setLineLength(recoveryDevice.getDiameter()); recoveryDevice.setCD(CD); config.setDeployAltitude(altitude / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); @@ -173,7 +174,7 @@ public class RecoveryHandler extends AbstractElementHandler { eventType = "Altitude"; warnings.add("Recovery device 2 is set to apogee, but recovery device 1 is also set to apogee. Setting recovery device 2 to altitude."); } - config.setDeployEvent(RASAeroCommonConstants.getDeployEventFromRASAero(eventType, warnings)); + config.setDeployEvent(RASAeroCommonConstants.RASAERO_TO_OPENROCKET_DEPLOY_EVENT(eventType, warnings)); // Shroud line count = diameter / 6 inches. 6 inches = 0.1524 meters. Minimum is 6 lines. recoveryDevice.setLineCount(Math.max(6, (int) Math.round(recoveryDevice.getDiameter() / 0.1524))); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index 8b8a26ec7..1bd623d80 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -3,6 +3,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java index 3205af2c3..dfd1d328b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -12,7 +13,7 @@ import net.sf.openrocket.rocketcomponent.RocketComponent; */ public abstract class SurfaceFinishHandler { public static void setSurfaceFinishes(Rocket rocket, String finish, WarningSet warnings) { - ExternalComponent.Finish surfaceFinish = RASAeroCommonConstants.getSurfaceFinishFromRASAero(finish, warnings); + ExternalComponent.Finish surfaceFinish = RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SURFACE(finish, warnings); for (RocketComponent component : rocket) { if (component instanceof ExternalComponent) { ((ExternalComponent) component).setFinish(surfaceFinish); diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java index 6f9893cc3..7afeebee8 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.importt; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -54,7 +55,7 @@ public class TransitionHandler extends BaseHandler { super.closeElement(element, attributes, content, warnings); try { if (RASAeroCommonConstants.REAR_DIAMETER.equals(element)) { - this.transition.setAftRadius(Double.parseDouble(content) / 2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); + this.transition.setAftRadius(Double.parseDouble(content) / 2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); @@ -64,8 +65,8 @@ public class TransitionHandler extends BaseHandler { @Override public void endHandler(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { super.endHandler(element, attributes, content, warnings); - this.transition.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); - this.transition.setForeRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TO_LENGTH); // Not really useful, but adding it for completeness + this.transition.setLength(length / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + this.transition.setForeRadius(diameter/2 / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); // Not really useful, but adding it for completeness this.transition.setForeRadiusAutomatic(true); this.transition.setThickness(0.002); // Arbitrary value; RASAero doesn't specify this } diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index eac3b3642..3bc6b357f 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -79,6 +79,7 @@ public abstract class Preferences implements ChangeSource { private static final String SHOW_DISCARD_CONFIRMATION = "IgnoreDiscardEditingWarning"; 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"; //Preferences related to 3D graphics @@ -223,6 +224,14 @@ public abstract class Preferences implements ChangeSource { public final void setLaunchIntoWind(boolean check) { this.putBoolean(LAUNCH_INTO_WIND, check); } + + public final boolean getShowRASAeroFormatWarning() { + return this.getBoolean(SHOW_RASAERO_FORMAT_WARNING, true); + } + + public final void setShowRASAeroFormatWarning(boolean check) { + this.putBoolean(SHOW_RASAERO_FORMAT_WARNING, check); + } public final boolean getShowRockSimFormatWarning() { return this.getBoolean(SHOW_ROCKSIM_FORMAT_WARNING, true); diff --git a/core/src/net/sf/openrocket/util/Color.java b/core/src/net/sf/openrocket/util/Color.java index 8d598cc77..7c3186d34 100644 --- a/core/src/net/sf/openrocket/util/Color.java +++ b/core/src/net/sf/openrocket/util/Color.java @@ -65,5 +65,16 @@ public class Color { public java.awt.Color toAWTColor() { return new java.awt.Color(red, green, blue, alpha); } - + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + return true; + } + if (!(obj instanceof Color)) { + return false; + } + Color c = (Color) obj; + return c.getRed() == getRed() && c.getGreen() == getGreen() && c.getBlue() == getBlue() && c.getAlpha() == getAlpha(); + } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java index de73270f9..f4893800a 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java @@ -228,6 +228,17 @@ public class GeneralPreferencesPanel extends PreferencesPanel { } }); this.add(openRecentOnStartupBox,"spanx, wrap"); + + //// Save RASAero Format warning dialog + final JCheckBox rasaeroWarningDialogBox = new JCheckBox(trans.get("pref.dlg.lbl.RASAeroWarning")); + rasaeroWarningDialogBox.setSelected(preferences.getShowRASAeroFormatWarning()); + rasaeroWarningDialogBox.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + preferences.setShowRASAeroFormatWarning(rasaeroWarningDialogBox.isSelected()); + } + }); + this.add(rasaeroWarningDialogBox,"spanx, wrap"); //// Save RockSim Format warning dialog final JCheckBox rocksimWarningDialogBox = new JCheckBox(trans.get("pref.dlg.lbl.RockSimWarning")); diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index dd3417dbf..2cec966ea 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -60,6 +60,7 @@ import net.sf.openrocket.document.events.DocumentChangeEvent; import net.sf.openrocket.document.events.DocumentChangeListener; import net.sf.openrocket.file.GeneralRocketSaver; import net.sf.openrocket.file.RocketLoadException; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; import net.sf.openrocket.gui.customexpression.CustomExpressionDialog; @@ -87,7 +88,6 @@ import net.sf.openrocket.gui.util.OpenFileWorker; import net.sf.openrocket.gui.util.SaveFileWorker; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.gui.util.URLUtil; -import net.sf.openrocket.gui.widgets.SaveFileChooser; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; @@ -407,18 +407,16 @@ public class BasicFrame extends JFrame { exportSubMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.desc")); exportSubMenu.setIcon(Icons.FILE_EXPORT); - /* Pending Future Development ////// Export RASAero - JMenuItem exportRASAero = new JMenuItem(trans.get("main.fileMenu.file.exportAs.RASAero")); - exportRASAero.setIcon(Icons.RASAERO - exportRASAero.getAccessibleContext().setAccessibleDescription(trans.get("main.fileMenu.file.exportAs.RASAero.desc")); + JMenuItem exportRASAero = new JMenuItem(trans.get("main.menu.file.exportAs.RASAero")); + exportRASAero.setIcon(Icons.RASAERO); + exportRASAero.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.RASAero.desc")); exportRASAero.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { exportRASAeroAction();} }); exportSubMenu.add(exportRASAero); - */ ////// Export RockSim JMenuItem exportRockSim = new JMenuItem(trans.get("main.menu.file.exportAs.RockSim")); @@ -1374,56 +1372,134 @@ public class BasicFrame extends JFrame { return saveAsOpenRocket(file); } + /** + * Opens a file chooser dialog for saving a new file, and returns the selected file. + * @param fileType file type to use (e.g. RASAero) + * @return the file selected from the dialog, or null if no file was selected. + */ + private File openFileSaveAsDialog(FileType fileType) { + final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType); - //// BEGIN RASAERO Export Action *** UNDER CONSTRUCTION -- CURRENTLY FOR TESTING ONLY *** + int option = chooser.showSaveDialog(BasicFrame.this); + + if (option != JFileChooser.APPROVE_OPTION) { + log.info(Markers.USER_MARKER, "User decided not to save, option=" + option); + return null; + } + + File file = chooser.getSelectedFile(); + if (file == null) { + log.info(Markers.USER_MARKER, "User did not select a file"); + return null; + } + + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); + + return file; + } + + + //// BEGIN RASAero Save/Export Action /** * MODEL "Export as" RASAero file format * * @return true if the file was saved, false otherwise */ - /* + public boolean exportRASAeroAction() { - Object exportRASAeroAction = ExportFileTranslator_RASAero.exportRASAeroAction; + File file = openFileSaveAsDialog(FileType.RASAERO); + if (file == null) { + return false; + } + + file = FileHelper.forceExtension(file, RASAeroCommonConstants.FILE_EXTENSION); + if (FileHelper.confirmWrite(file, this) ) { + return saveAsRASAero(file); + } return false; } - */ - //// END RASAERO Export Action + + /** + * Perform the writing of the design to the given file in RASAero format. + * @param file the chosen file + * @return true if the file was written + */ + private boolean saveAsRASAero(File file) { + if (prefs.getShowRASAeroFormatWarning()) { + // Show RASAero format warning + JPanel panel = new JPanel(new MigLayout()); + panel.add(new StyledLabel(trans.get("SaveRASAeroWarningDialog.txt1")), "wrap"); + final JCheckBox check = new JCheckBox(trans.get("SaveRASAeroWarningDialog.donotshow")); + check.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + prefs.setShowRASAeroFormatWarning(!check.isSelected()); + } + }); + panel.add(check); + int sel = JOptionPane.showOptionDialog(null, + panel, + "", // title + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, + null, // icon + null, // options + null // default option + ); + if (sel == 1) { + return false; + } + } + + StorageOptions options = new StorageOptions(); + options.setFileType(FileType.RASAERO); + return saveRASAeroFile(file, options); + } + + /** + * Perform the actual saving of the RASAero file + * @param file file to be stored + * @param options storage options to use + * @return true if the file was written + */ + private boolean saveRASAeroFile(File file, StorageOptions options) { + try { + ROCKET_SAVER.save(file, document, options); + // Do not update the save state of the document. + return true; + } catch (IOException e) { + return false; + } catch (DecalNotFoundException decex) { + DecalImage decal = decex.getDecal(); + // Check if the user replaced the source file, if not, just ignore the faulty decal on the next save + if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) { + decal.setIgnored(true); + } + return saveRASAeroFile(file, options); // Re-save + } + } + //// END RASAero Save/Export Action - //// BEGIN ROCKSIM Export Action + //// BEGIN ROCKSIM Save/Export Action /** * MODEL "Export as" RASAero file format * * @return true if the file was saved, false otherwise */ public boolean exportRockSimAction() { - File file; - - final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, FileType.ROCKSIM); - - int option = chooser.showSaveDialog(BasicFrame.this); - - if (option != JFileChooser.APPROVE_OPTION) { - log.info(Markers.USER_MARKER, "User decided not to save, option=" + option); - return false; - } - - file = chooser.getSelectedFile(); + File file = openFileSaveAsDialog(FileType.ROCKSIM); if (file == null) { - log.info(Markers.USER_MARKER, "User did not select a file"); return false; } - ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); - file = FileHelper.forceExtension(file, "rkt"); if (FileHelper.confirmWrite(file, this) ) { return saveAsRockSim(file); } return false; } - // END ROCKSIM Export Action /** * Perform the writing of the design to the given file in RockSim format. @@ -1488,6 +1564,8 @@ public class BasicFrame extends JFrame { } } + //// END ROCKSIM Save/Export Action + /** * "Save As" action. diff --git a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java index 7202e02f9..52a8d3c29 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java @@ -53,13 +53,13 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { this.addChoosableFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER); this.setFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER); break; - /*case RASAERO: + case RASAERO: defaultFilename = FileHelper.forceExtension(defaultFilename,"CDX1"); this.setDialogTitle(trans.get("saveAs.rasaero.title")); storageChooser = null; this.addChoosableFileFilter(FileHelper.RASAERO_DESIGN_FILTER); this.setFileFilter(FileHelper.RASAERO_DESIGN_FILTER); - break;*/ + break; } final RememberFilenamePropertyListener listener = new RememberFilenamePropertyListener(); From bc3e812fe5d96016a8c89a3bc1315d566f1702fb Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 26 Mar 2023 21:42:59 +0200 Subject: [PATCH 05/76] Move WarningSet and Warning to dedicated package --- .../aerodynamics/AbstractAerodynamicCalculator.java | 1 + .../aerodynamics/AerodynamicCalculator.java | 2 +- .../openrocket/aerodynamics/BarrowmanCalculator.java | 3 ++- .../aerodynamics/barrowman/ComponentAssemblyCalc.java | 2 +- .../openrocket/aerodynamics/barrowman/FinSetCalc.java | 5 ++--- .../aerodynamics/barrowman/LaunchLugCalc.java | 4 +--- .../aerodynamics/barrowman/RailButtonCalc.java | 2 +- .../aerodynamics/barrowman/RocketComponentCalc.java | 2 +- .../barrowman/SymmetricComponentCalc.java | 4 ++-- .../openrocket/aerodynamics/barrowman/TubeCalc.java | 2 +- .../aerodynamics/barrowman/TubeFinSetCalc.java | 11 ++--------- .../openrocket/communication/ReleaseNotesHandler.java | 2 +- .../communication/WelcomeInfoRetriever.java | 2 +- core/src/net/sf/openrocket/document/Simulation.java | 2 +- .../net/sf/openrocket/file/AbstractRocketLoader.java | 2 +- core/src/net/sf/openrocket/file/CSVExport.java | 4 ++-- .../net/sf/openrocket/file/DatabaseMotorFinder.java | 5 ++--- .../net/sf/openrocket/file/GeneralRocketLoader.java | 2 +- core/src/net/sf/openrocket/file/MotorFinder.java | 2 +- core/src/net/sf/openrocket/file/RocketLoader.java | 2 +- core/src/net/sf/openrocket/file/RocketSaver.java | 2 +- .../sf/openrocket/file/motor/RockSimMotorLoader.java | 2 +- .../openrocket/file/openrocket/OpenRocketSaver.java | 2 +- .../file/openrocket/importt/AnglePositionSetter.java | 2 +- .../file/openrocket/importt/AppearanceHandler.java | 2 +- .../file/openrocket/importt/AtmosphereHandler.java | 2 +- .../file/openrocket/importt/AxialPositionSetter.java | 4 ++-- .../file/openrocket/importt/BooleanSetter.java | 4 ++-- .../importt/ClusterConfigurationSetter.java | 2 +- .../file/openrocket/importt/ColorSetter.java | 4 ++-- .../file/openrocket/importt/ComponentHandler.java | 4 ++-- .../openrocket/importt/ComponentParameterHandler.java | 4 ++-- .../openrocket/importt/ComponentPresetSetter.java | 4 ++-- .../file/openrocket/importt/ConfigHandler.java | 2 +- .../openrocket/importt/CustomExpressionHandler.java | 2 +- .../file/openrocket/importt/DatatypeHandler.java | 4 ++-- .../importt/DeploymentConfigurationHandler.java | 4 ++-- .../file/openrocket/importt/DoubleSetter.java | 4 ++-- .../file/openrocket/importt/EnumSetter.java | 4 ++-- .../file/openrocket/importt/FinSetPointHandler.java | 4 ++-- .../file/openrocket/importt/FinTabPositionSetter.java | 2 +- .../openrocket/importt/FlightDataBranchHandler.java | 2 +- .../file/openrocket/importt/FlightDataHandler.java | 4 ++-- .../importt/IgnitionConfigurationHandler.java | 4 ++-- .../openrocket/importt/InsideAppearanceHandler.java | 8 +------- .../openrocket/file/openrocket/importt/IntSetter.java | 4 ++-- .../file/openrocket/importt/MaterialSetter.java | 4 ++-- .../openrocket/importt/MotorConfigurationHandler.java | 5 ++--- .../file/openrocket/importt/MotorHandler.java | 4 ++-- .../file/openrocket/importt/MotorMountHandler.java | 4 ++-- .../openrocket/importt/OpenRocketContentHandler.java | 4 ++-- .../file/openrocket/importt/OpenRocketHandler.java | 4 ++-- .../file/openrocket/importt/OverrideSetter.java | 4 ++-- .../file/openrocket/importt/PhotoStudioHandler.java | 2 +- .../file/openrocket/importt/RadiusPositionSetter.java | 2 +- .../sf/openrocket/file/openrocket/importt/Setter.java | 2 +- .../importt/SimulationConditionsHandler.java | 2 +- .../file/openrocket/importt/SimulationsHandler.java | 2 +- .../openrocket/importt/SingleSimulationHandler.java | 2 +- .../importt/StageSeparationConfigurationHandler.java | 4 ++-- .../file/openrocket/importt/StringSetter.java | 2 +- .../file/rasaero/RASAeroCommonConstants.java | 2 +- .../openrocket/file/rasaero/importt/BaseHandler.java | 2 +- .../file/rasaero/importt/BoattailHandler.java | 2 +- .../file/rasaero/importt/BodyTubeHandler.java | 2 +- .../file/rasaero/importt/BoosterHandler.java | 2 +- .../file/rasaero/importt/FinCanHandler.java | 2 +- .../openrocket/file/rasaero/importt/FinHandler.java | 2 +- .../file/rasaero/importt/LaunchSiteHandler.java | 2 +- .../file/rasaero/importt/NoseConeHandler.java | 2 +- .../file/rasaero/importt/RASAeroHandler.java | 2 +- .../file/rasaero/importt/RASAeroMotorsLoader.java | 2 +- .../file/rasaero/importt/RecoveryHandler.java | 2 +- .../file/rasaero/importt/SimulationListHandler.java | 2 +- .../file/rasaero/importt/SurfaceFinishHandler.java | 2 +- .../file/rasaero/importt/TransitionHandler.java | 2 +- .../file/rocksim/importt/AttachedPartsHandler.java | 2 +- .../openrocket/file/rocksim/importt/BaseHandler.java | 2 +- .../file/rocksim/importt/BodyTubeHandler.java | 2 +- .../file/rocksim/importt/FinSetHandler.java | 2 +- .../file/rocksim/importt/InnerBodyTubeHandler.java | 2 +- .../file/rocksim/importt/LaunchLugHandler.java | 2 +- .../file/rocksim/importt/MassObjectHandler.java | 2 +- .../file/rocksim/importt/NoseConeHandler.java | 2 +- .../file/rocksim/importt/ParachuteHandler.java | 2 +- .../openrocket/file/rocksim/importt/PodHandler.java | 2 +- .../rocksim/importt/PositionDependentHandler.java | 2 +- .../file/rocksim/importt/RecoveryDeviceHandler.java | 2 +- .../openrocket/file/rocksim/importt/RingHandler.java | 2 +- .../rocksim/importt/RockSimAppearanceBuilder.java | 2 +- .../file/rocksim/importt/RockSimHandler.java | 4 ++-- .../file/rocksim/importt/StreamerHandler.java | 2 +- .../file/rocksim/importt/SubAssemblyHandler.java | 2 +- .../file/rocksim/importt/TransitionHandler.java | 2 +- .../file/rocksim/importt/TubeFinSetHandler.java | 2 +- .../file/simplesax/AbstractElementHandler.java | 4 ++-- .../openrocket/file/simplesax/DelegatorHandler.java | 2 +- .../sf/openrocket/file/simplesax/ElementHandler.java | 2 +- .../openrocket/file/simplesax/NullElementHandler.java | 4 ++-- .../openrocket/file/simplesax/PlainTextHandler.java | 4 ++-- .../net/sf/openrocket/file/simplesax/SimpleSAX.java | 2 +- .../openrocket/{aerodynamics => logging}/Warning.java | 2 +- .../{aerodynamics => logging}/WarningSet.java | 2 +- .../simulation/BasicEventSimulationEngine.java | 6 ++---- core/src/net/sf/openrocket/simulation/FlightData.java | 2 +- .../openrocket/simulation/RK4SimulationStepper.java | 2 +- .../sf/openrocket/simulation/SimulationStatus.java | 6 +----- .../extension/AbstractSimulationExtension.java | 2 +- .../simulation/extension/SimulationExtension.java | 2 +- .../simulation/extension/example/DampingMoment.java | 1 - .../simulation/extension/impl/ScriptingExtension.java | 4 ++-- .../listeners/SimulationListenerHelper.java | 2 +- .../thrustcurve/DownloadResponseParser.java | 2 +- .../openrocket/thrustcurve/SearchResponseParser.java | 2 +- .../aerodynamics/BarrowmanCalculatorTest.java | 1 + .../sf/openrocket/aerodynamics/FinSetCalcTest.java | 2 ++ .../openrocket/aerodynamics/RailButtonCalcTest.java | 4 ++-- .../aerodynamics/SymmetricComponentCalcTest.java | 2 ++ .../file/rocksim/importt/BodyTubeHandlerTest.java | 2 +- .../file/rocksim/importt/FinSetHandlerTest.java | 2 +- .../rocksim/importt/InnerBodyTubeHandlerTest.java | 2 +- .../file/rocksim/importt/LaunchLugHandlerTest.java | 2 +- .../file/rocksim/importt/MassObjectHandlerTest.java | 2 +- .../file/rocksim/importt/NoseConeHandlerTest.java | 2 +- .../file/rocksim/importt/ParachuteHandlerTest.java | 2 +- .../file/rocksim/importt/RingHandlerTest.java | 2 +- .../file/rocksim/importt/StreamerHandlerTest.java | 2 +- .../file/rocksim/importt/TransitionHandlerTest.java | 2 +- .../file/rocksim/importt/TubeFinSetHandlerTest.java | 2 +- .../rocketcomponent/FreeformFinSetTest.java | 2 +- .../sf/openrocket/rocketcomponent/OverrideTest.java | 2 +- .../net/sf/openrocket/simulation/TestFlightData.java | 2 +- .../sf/openrocket/file/SimulationTableCSVExport.java | 4 ++-- .../gui/dialogs/ComponentAnalysisDialog.java | 5 ++--- .../net/sf/openrocket/gui/dialogs/WarningDialog.java | 4 ++-- .../sf/openrocket/gui/figureelements/RocketInfo.java | 4 ++-- swing/src/net/sf/openrocket/gui/main/BasicFrame.java | 2 +- .../net/sf/openrocket/gui/main/SimulationPanel.java | 4 ++-- .../sf/openrocket/gui/scalefigure/RocketPanel.java | 2 +- .../gui/simulation/SimulationWarningDialog.java | 2 +- 140 files changed, 181 insertions(+), 200 deletions(-) rename core/src/net/sf/openrocket/{aerodynamics => logging}/Warning.java (99%) rename core/src/net/sf/openrocket/{aerodynamics => logging}/WarningSet.java (98%) diff --git a/core/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java index 978fd0900..6e7c2db97 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java @@ -2,6 +2,7 @@ package net.sf.openrocket.aerodynamics; import java.util.Map; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.MathUtil; diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index 76429c276..8564edf74 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -2,8 +2,8 @@ package net.sf.openrocket.aerodynamics; import java.util.Map; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Monitorable; diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 97d7dd303..22e21cf39 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -1,10 +1,11 @@ package net.sf.openrocket.aerodynamics; -import static net.sf.openrocket.util.MathUtil.EPSILON; import static net.sf.openrocket.util.MathUtil.pow2; import java.util.*; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java index 2017bb168..c6f113094 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/ComponentAssemblyCalc.java @@ -2,7 +2,7 @@ package net.sf.openrocket.aerodynamics.barrowman; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Transformation; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 326c73a0d..59072fd9b 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -1,15 +1,14 @@ package net.sf.openrocket.aerodynamics.barrowman; import static java.lang.Math.pow; -import static net.sf.openrocket.util.MathUtil.EPSILON; import static net.sf.openrocket.util.MathUtil.pow2; import java.util.Arrays; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.BugException; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java index 04c3425ec..6a178cf44 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java @@ -2,10 +2,8 @@ package net.sf.openrocket.aerodynamics.barrowman; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; -import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; public class LaunchLugCalc extends TubeCalc { diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java index 26a279fbf..9f6c7bbd3 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Coordinate; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java index 68b5daede..1af683bfa 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java @@ -2,7 +2,7 @@ package net.sf.openrocket.aerodynamics.barrowman; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Transformation; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java index 2820ce5cb..41a3a81e2 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java @@ -5,8 +5,8 @@ import static net.sf.openrocket.util.MathUtil.pow2; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeCalc.java index cce711b4d..d1875c9e4 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeCalc.java @@ -1,7 +1,7 @@ package net.sf.openrocket.aerodynamics.barrowman; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Tube; import net.sf.openrocket.util.MathUtil; diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java index 88e402f1d..642664ce5 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java @@ -1,23 +1,16 @@ package net.sf.openrocket.aerodynamics.barrowman; -import static java.lang.Math.pow; import static net.sf.openrocket.util.MathUtil.pow2; -import java.util.Arrays; - import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.Warning.Other; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.TubeFinSet; import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.LinearInterpolator; import net.sf.openrocket.util.MathUtil; -import net.sf.openrocket.util.PolyInterpolator; import net.sf.openrocket.util.Transformation; import org.slf4j.Logger; diff --git a/core/src/net/sf/openrocket/communication/ReleaseNotesHandler.java b/core/src/net/sf/openrocket/communication/ReleaseNotesHandler.java index ca24fd731..409d82a7a 100644 --- a/core/src/net/sf/openrocket/communication/ReleaseNotesHandler.java +++ b/core/src/net/sf/openrocket/communication/ReleaseNotesHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.communication; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/communication/WelcomeInfoRetriever.java b/core/src/net/sf/openrocket/communication/WelcomeInfoRetriever.java index 38736eff7..0642695e1 100644 --- a/core/src/net/sf/openrocket/communication/WelcomeInfoRetriever.java +++ b/core/src/net/sf/openrocket/communication/WelcomeInfoRetriever.java @@ -1,6 +1,6 @@ package net.sf.openrocket.communication; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.SimpleSAX; import net.sf.openrocket.util.BuildProperties; import org.xml.sax.InputSource; diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index 068fc91c8..b560cd1a8 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.masscalc.MassCalculator; import net.sf.openrocket.rocketcomponent.FlightConfiguration; diff --git a/core/src/net/sf/openrocket/file/AbstractRocketLoader.java b/core/src/net/sf/openrocket/file/AbstractRocketLoader.java index c5d206ccd..38e55ffa4 100644 --- a/core/src/net/sf/openrocket/file/AbstractRocketLoader.java +++ b/core/src/net/sf/openrocket/file/AbstractRocketLoader.java @@ -3,7 +3,7 @@ package net.sf.openrocket.file; import java.io.IOException; import java.io.InputStream; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; public abstract class AbstractRocketLoader implements RocketLoader { diff --git a/core/src/net/sf/openrocket/file/CSVExport.java b/core/src/net/sf/openrocket/file/CSVExport.java index 2536450eb..b8e1cc4a1 100644 --- a/core/src/net/sf/openrocket/file/CSVExport.java +++ b/core/src/net/sf/openrocket/file/CSVExport.java @@ -8,8 +8,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; diff --git a/core/src/net/sf/openrocket/file/DatabaseMotorFinder.java b/core/src/net/sf/openrocket/file/DatabaseMotorFinder.java index 36e83698d..0112e398b 100644 --- a/core/src/net/sf/openrocket/file/DatabaseMotorFinder.java +++ b/core/src/net/sf/openrocket/file/DatabaseMotorFinder.java @@ -1,13 +1,12 @@ package net.sf.openrocket.file; -import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.Motor.Type; import net.sf.openrocket.startup.Application; diff --git a/core/src/net/sf/openrocket/file/GeneralRocketLoader.java b/core/src/net/sf/openrocket/file/GeneralRocketLoader.java index d4bd10f96..7de369c58 100644 --- a/core/src/net/sf/openrocket/file/GeneralRocketLoader.java +++ b/core/src/net/sf/openrocket/file/GeneralRocketLoader.java @@ -12,7 +12,7 @@ import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.OpenRocketDocumentFactory; import net.sf.openrocket.file.openrocket.importt.OpenRocketLoader; diff --git a/core/src/net/sf/openrocket/file/MotorFinder.java b/core/src/net/sf/openrocket/file/MotorFinder.java index b17e66433..5c1ec08e5 100644 --- a/core/src/net/sf/openrocket/file/MotorFinder.java +++ b/core/src/net/sf/openrocket/file/MotorFinder.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Motor; /** diff --git a/core/src/net/sf/openrocket/file/RocketLoader.java b/core/src/net/sf/openrocket/file/RocketLoader.java index 1bd5c2460..81453a7d6 100644 --- a/core/src/net/sf/openrocket/file/RocketLoader.java +++ b/core/src/net/sf/openrocket/file/RocketLoader.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file; import java.io.InputStream; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; public interface RocketLoader { diff --git a/core/src/net/sf/openrocket/file/RocketSaver.java b/core/src/net/sf/openrocket/file/RocketSaver.java index 1ccb0e41b..fa1d4b56b 100644 --- a/core/src/net/sf/openrocket/file/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/RocketSaver.java @@ -3,7 +3,7 @@ package net.sf.openrocket.file; import java.io.IOException; import java.io.OutputStream; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; diff --git a/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java b/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java index b1fee2686..561d0f2e9 100644 --- a/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java +++ b/core/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java @@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.NullElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 3e9c82389..7cb022c44 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -11,7 +11,7 @@ import net.sf.openrocket.file.openrocket.savers.PhotoStudioSaver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.logging.Warning; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.StorageOptions; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/AnglePositionSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/AnglePositionSetter.java index 4843f6872..d49e35359 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/AnglePositionSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/AnglePositionSetter.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.position.AngleMethod; import net.sf.openrocket.rocketcomponent.position.AnglePositionable; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/AppearanceHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/AppearanceHandler.java index 4d7445d14..5db14f599 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/AppearanceHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/AppearanceHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.appearance.AppearanceBuilder; import net.sf.openrocket.appearance.Decal.EdgeMode; import net.sf.openrocket.document.Attachment; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/AtmosphereHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/AtmosphereHandler.java index b109c1c24..dd86befc6 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/AtmosphereHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/AtmosphereHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/AxialPositionSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/AxialPositionSetter.java index 2fe5084a9..c031b0215 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/AxialPositionSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/AxialPositionSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.rocketcomponent.position.AxialPositionable; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java index 8de389fa8..ecadcd7ea 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/BooleanSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ClusterConfigurationSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/ClusterConfigurationSetter.java index 4fce5c064..ba2a4cb26 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ClusterConfigurationSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ClusterConfigurationSetter.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.ClusterConfiguration; import net.sf.openrocket.rocketcomponent.Clusterable; import net.sf.openrocket.rocketcomponent.RocketComponent; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ColorSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/ColorSetter.java index f290162e0..041386cbd 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ColorSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ColorSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Color; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentHandler.java index 85d782bb0..729a56880 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentHandler.java @@ -4,8 +4,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentParameterHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentParameterHandler.java index dfc6b40d1..34bbee014 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentParameterHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentParameterHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java index b90940629..097338116 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ComponentPresetSetter.java @@ -3,8 +3,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; import java.util.List; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java index f9df591e7..6c58bf129 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/ConfigHandler.java @@ -5,7 +5,7 @@ import java.math.BigInteger; import java.util.HashMap; import java.util.List; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/CustomExpressionHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/CustomExpressionHandler.java index 187f2ffff..61d849b7d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/CustomExpressionHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/CustomExpressionHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DatatypeHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/DatatypeHandler.java index c3fef5140..652f5d2e5 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DatatypeHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DatatypeHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DeploymentConfigurationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/DeploymentConfigurationHandler.java index 77b157369..8e9e54f7e 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DeploymentConfigurationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DeploymentConfigurationHandler.java @@ -4,8 +4,8 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DoubleSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/DoubleSetter.java index b12263666..688b6b78d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DoubleSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DoubleSetter.java @@ -3,8 +3,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.Arrays; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FlightConfigurableParameterSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/EnumSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/EnumSetter.java index 3156c1dd2..5966c1fe7 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/EnumSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/EnumSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FlightConfigurableParameterSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FinSetPointHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FinSetPointHandler.java index 05f3386e2..bc2d51c45 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FinSetPointHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FinSetPointHandler.java @@ -5,8 +5,8 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FinTabPositionSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/FinTabPositionSetter.java index c756d2683..1de1182ee 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FinTabPositionSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FinTabPositionSetter.java @@ -5,7 +5,7 @@ import java.util.HashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.position.*; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java index 44cb2feaa..d44303c9b 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java index 99d819327..c5d766458 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java @@ -4,8 +4,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/IgnitionConfigurationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/IgnitionConfigurationHandler.java index 10d5c6b1a..ca0237a01 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/IgnitionConfigurationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/IgnitionConfigurationHandler.java @@ -4,8 +4,8 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/InsideAppearanceHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/InsideAppearanceHandler.java index 9940a30cb..ac9fae327 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/InsideAppearanceHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/InsideAppearanceHandler.java @@ -1,13 +1,7 @@ package net.sf.openrocket.file.openrocket.importt; -import net.sf.openrocket.aerodynamics.WarningSet; -import net.sf.openrocket.appearance.AppearanceBuilder; -import net.sf.openrocket.appearance.Decal; -import net.sf.openrocket.document.Attachment; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; -import net.sf.openrocket.file.simplesax.AbstractElementHandler; -import net.sf.openrocket.file.simplesax.ElementHandler; -import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.InsideColorComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/IntSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/IntSetter.java index c3987342e..893dfc308 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/IntSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/IntSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/MaterialSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/MaterialSetter.java index 3aae93c04..0c6792d91 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/MaterialSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/MaterialSetter.java @@ -3,8 +3,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; import java.util.Locale; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.database.Databases; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.RocketComponent; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java index 1b0a4a2dd..96f212b14 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/MotorConfigurationHandler.java @@ -4,13 +4,12 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.Rocket; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java index 5e4258cb3..5b2e2d4a2 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java @@ -5,8 +5,8 @@ import java.lang.Double; import java.util.HashMap; import java.util.Locale; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/MotorMountHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/MotorMountHandler.java index 93b51fd08..f95a6645d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/MotorMountHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/MotorMountHandler.java @@ -4,8 +4,8 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketContentHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketContentHandler.java index f4745bf16..b14bc6bb4 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketContentHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketContentHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketHandler.java index f9c2a56be..d3742aeb6 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketHandler.java @@ -8,8 +8,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OverrideSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/OverrideSetter.java index 1cdf8585b..5b74a58ef 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OverrideSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OverrideSetter.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/PhotoStudioHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/PhotoStudioHandler.java index 52f729be1..ca6add7d3 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/PhotoStudioHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/PhotoStudioHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.openrocket.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/RadiusPositionSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/RadiusPositionSetter.java index bd4f36404..25dc4c813 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/RadiusPositionSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/RadiusPositionSetter.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.position.RadiusMethod; import net.sf.openrocket.rocketcomponent.position.RadiusPositionable; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/Setter.java b/core/src/net/sf/openrocket/file/openrocket/importt/Setter.java index 79dc82aa0..fea7cbd48 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/Setter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/Setter.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; //// Interface diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/SimulationConditionsHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/SimulationConditionsHandler.java index 941abd51f..8a82302b6 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/SimulationConditionsHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/SimulationConditionsHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/SimulationsHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/SimulationsHandler.java index c5b0f705b..eaf8911f7 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/SimulationsHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/SimulationsHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java index f24edfbc7..7c1cee0ee 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/SingleSimulationHandler.java @@ -5,7 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Set; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.Simulation.Status; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/StageSeparationConfigurationHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/StageSeparationConfigurationHandler.java index f17753b39..57c913549 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/StageSeparationConfigurationHandler.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/StageSeparationConfigurationHandler.java @@ -4,8 +4,8 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/StringSetter.java b/core/src/net/sf/openrocket/file/openrocket/importt/StringSetter.java index b1c6fc1c4..4e1fa021d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/StringSetter.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/StringSetter.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.openrocket.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Reflection; diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index d42161a64..da5481ca8 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.FinSet; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java index c09f69003..56f67569b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BaseHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java index 60c688dde..db3dad2e3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BoattailHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java index b5f080418..0f9635eea 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BodyTubeHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java index b8cb625d2..30121dc98 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/BoosterHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java index f1d9699d9..f79860300 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinCanHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.BodyTube; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java index b650846aa..22ba7a807 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/FinHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java index 81c856c3b..79e157880 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/LaunchSiteHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java index f8ffb19c4..d112a0e97 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/NoseConeHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java index bc4a9a8cc..751e37cfe 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java index cb608674b..b24df532f 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.database.motor.ThrustCurveMotorSet; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.startup.Application; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java index ed346a3bd..6af7ccd45 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index 1bd623d80..a33efc456 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java index dfd1d328b..4306b75c0 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SurfaceFinishHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.Rocket; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java index 7afeebee8..91bfe1c73 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/TransitionHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java index d90b8b7c5..27ecb0e81 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/AttachedPartsHandler.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/BaseHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/BaseHandler.java index c8394e076..8e3b9ff43 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/BaseHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/BaseHandler.java @@ -7,7 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.database.Databases; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java index 093858955..ea90c5227 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/BodyTubeHandler.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/FinSetHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/FinSetHandler.java index f3305713d..d503112bb 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/FinSetHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/FinSetHandler.java @@ -11,7 +11,7 @@ import java.util.List; import net.sf.openrocket.rocketcomponent.Transition; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandler.java index 924596012..21120fe3d 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandler.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/LaunchLugHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/LaunchLugHandler.java index 1004edfca..29c154540 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/LaunchLugHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/LaunchLugHandler.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/MassObjectHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/MassObjectHandler.java index 8ad7b1ff8..e2b1895cc 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/MassObjectHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/MassObjectHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimDensityType; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/NoseConeHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/NoseConeHandler.java index c9b5b040d..59a6a4469 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/NoseConeHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/NoseConeHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/ParachuteHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/ParachuteHandler.java index 5d4a7af7a..8a8006a35 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/ParachuteHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/ParachuteHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java index ffec3d0b5..267be2ec6 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/PodHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java index 2bdd3a674..ba56d4f82 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/PositionDependentHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimLocationMode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/RecoveryDeviceHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/RecoveryDeviceHandler.java index 871f88b65..3f3ca4968 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/RecoveryDeviceHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/RecoveryDeviceHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimDensityType; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/RingHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/RingHandler.java index 9ffa798f0..2afb02c5a 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/RingHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/RingHandler.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java index 57dacf7f1..00b337f7e 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java @@ -4,7 +4,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.net.MalformedURLException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.appearance.AppearanceBuilder; import net.sf.openrocket.appearance.Decal.EdgeMode; import net.sf.openrocket.document.Attachment; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimHandler.java index 18f785b2c..c175cc1c9 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimHandler.java @@ -6,8 +6,8 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/StreamerHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/StreamerHandler.java index 3730bf36c..e3418941f 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/StreamerHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/StreamerHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java index a80f3034d..c57a9ae2c 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/SubAssemblyHandler.java @@ -1,7 +1,7 @@ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.ElementHandler; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/TransitionHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/TransitionHandler.java index 7fe0d81d3..1dfb182db 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/TransitionHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/TransitionHandler.java @@ -5,7 +5,7 @@ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandler.java b/core/src/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandler.java index 7d78212fe..29f2a1ba9 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandler.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandler.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimFinishCode; diff --git a/core/src/net/sf/openrocket/file/simplesax/AbstractElementHandler.java b/core/src/net/sf/openrocket/file/simplesax/AbstractElementHandler.java index 3085d2ffe..9e926dcd9 100644 --- a/core/src/net/sf/openrocket/file/simplesax/AbstractElementHandler.java +++ b/core/src/net/sf/openrocket/file/simplesax/AbstractElementHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.simplesax; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java b/core/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java index 6023f43ee..b55d215c3 100644 --- a/core/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java +++ b/core/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java @@ -8,7 +8,7 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; /** * The actual SAX handler class. Contains the necessary methods for parsing the SAX source. diff --git a/core/src/net/sf/openrocket/file/simplesax/ElementHandler.java b/core/src/net/sf/openrocket/file/simplesax/ElementHandler.java index de963949f..bd05f2eac 100644 --- a/core/src/net/sf/openrocket/file/simplesax/ElementHandler.java +++ b/core/src/net/sf/openrocket/file/simplesax/ElementHandler.java @@ -2,7 +2,7 @@ package net.sf.openrocket.file.simplesax; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/file/simplesax/NullElementHandler.java b/core/src/net/sf/openrocket/file/simplesax/NullElementHandler.java index 302ee91c9..623b9aeac 100644 --- a/core/src/net/sf/openrocket/file/simplesax/NullElementHandler.java +++ b/core/src/net/sf/openrocket/file/simplesax/NullElementHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.simplesax; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/file/simplesax/PlainTextHandler.java b/core/src/net/sf/openrocket/file/simplesax/PlainTextHandler.java index 406a522c9..76846a117 100644 --- a/core/src/net/sf/openrocket/file/simplesax/PlainTextHandler.java +++ b/core/src/net/sf/openrocket/file/simplesax/PlainTextHandler.java @@ -2,8 +2,8 @@ package net.sf.openrocket.file.simplesax; import java.util.HashMap; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; /** * An element handler that does not allow any sub-elements. If any are encountered diff --git a/core/src/net/sf/openrocket/file/simplesax/SimpleSAX.java b/core/src/net/sf/openrocket/file/simplesax/SimpleSAX.java index cc1fc55f6..f2d0ebb76 100644 --- a/core/src/net/sf/openrocket/file/simplesax/SimpleSAX.java +++ b/core/src/net/sf/openrocket/file/simplesax/SimpleSAX.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import org.xml.sax.InputSource; import org.xml.sax.SAXException; diff --git a/core/src/net/sf/openrocket/aerodynamics/Warning.java b/core/src/net/sf/openrocket/logging/Warning.java similarity index 99% rename from core/src/net/sf/openrocket/aerodynamics/Warning.java rename to core/src/net/sf/openrocket/logging/Warning.java index 71946ca5b..8d053758c 100644 --- a/core/src/net/sf/openrocket/aerodynamics/Warning.java +++ b/core/src/net/sf/openrocket/logging/Warning.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.aerodynamics; +package net.sf.openrocket.logging; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Motor; diff --git a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java b/core/src/net/sf/openrocket/logging/WarningSet.java similarity index 98% rename from core/src/net/sf/openrocket/aerodynamics/WarningSet.java rename to core/src/net/sf/openrocket/logging/WarningSet.java index 6fcd7b837..2c248786b 100644 --- a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java +++ b/core/src/net/sf/openrocket/logging/WarningSet.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.aerodynamics; +package net.sf.openrocket.logging; import java.util.AbstractSet; import java.util.Iterator; diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 6838c4a2b..827771438 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -1,17 +1,15 @@ package net.sf.openrocket.simulation; import java.util.ArrayDeque; -import java.util.Collection; import java.util.Deque; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationId; import net.sf.openrocket.rocketcomponent.AxialStage; diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index 27f6c1318..9f6c2386a 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -6,7 +6,7 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Mutable; diff --git a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 80275a52b..2bfa14b66 100644 --- a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.masscalc.RigidBody; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index 4cb132312..772978c23 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -7,12 +7,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationId; import net.sf.openrocket.rocketcomponent.FlightConfiguration; diff --git a/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java b/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java index fbed23a43..655b21527 100644 --- a/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java +++ b/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java @@ -3,7 +3,7 @@ package net.sf.openrocket.simulation.extension; import java.util.Collections; import java.util.List; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.l10n.Translator; diff --git a/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java b/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java index f0b6c93c2..e960432cb 100644 --- a/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java +++ b/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java @@ -2,7 +2,7 @@ package net.sf.openrocket.simulation.extension; import java.util.List; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.simulation.FlightDataType; diff --git a/core/src/net/sf/openrocket/simulation/extension/example/DampingMoment.java b/core/src/net/sf/openrocket/simulation/extension/example/DampingMoment.java index a6e0429fb..403195797 100644 --- a/core/src/net/sf/openrocket/simulation/extension/example/DampingMoment.java +++ b/core/src/net/sf/openrocket/simulation/extension/example/DampingMoment.java @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.RocketComponent; diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java index 953543442..4addd22a5 100644 --- a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java +++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java @@ -4,8 +4,8 @@ import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptException; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.l10n.L10N; diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java index 903971481..ea46bb79f 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.logging.Warning; import net.sf.openrocket.masscalc.RigidBody; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorConfigurationId; diff --git a/core/src/net/sf/openrocket/thrustcurve/DownloadResponseParser.java b/core/src/net/sf/openrocket/thrustcurve/DownloadResponseParser.java index 6a851b6a3..660b47380 100644 --- a/core/src/net/sf/openrocket/thrustcurve/DownloadResponseParser.java +++ b/core/src/net/sf/openrocket/thrustcurve/DownloadResponseParser.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.SimpleSAX; diff --git a/core/src/net/sf/openrocket/thrustcurve/SearchResponseParser.java b/core/src/net/sf/openrocket/thrustcurve/SearchResponseParser.java index 03dd75643..a7015c2f1 100644 --- a/core/src/net/sf/openrocket/thrustcurve/SearchResponseParser.java +++ b/core/src/net/sf/openrocket/thrustcurve/SearchResponseParser.java @@ -9,7 +9,7 @@ import java.util.HashMap; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.SimpleSAX; diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index ea423886c..eba208790 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import net.sf.openrocket.logging.WarningSet; import org.junit.BeforeClass; import org.junit.Test; diff --git a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java index bfdefe084..4cd2aa885 100644 --- a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java @@ -1,6 +1,8 @@ package net.sf.openrocket.aerodynamics; import static org.junit.Assert.assertEquals; + +import net.sf.openrocket.logging.WarningSet; import org.junit.BeforeClass; import org.junit.Test; diff --git a/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java index 513b0c922..588741449 100644 --- a/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java @@ -1,6 +1,8 @@ package net.sf.openrocket.aerodynamics; import static org.junit.Assert.assertEquals; + +import net.sf.openrocket.logging.WarningSet; import org.junit.BeforeClass; import org.junit.Test; @@ -9,7 +11,6 @@ import com.google.inject.Injector; import com.google.inject.Module; import net.sf.openrocket.ServicesForTesting; -import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.barrowman.RailButtonCalc; import net.sf.openrocket.plugin.PluginModule; import net.sf.openrocket.rocketcomponent.FlightConfiguration; @@ -21,7 +22,6 @@ import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.TestRockets; -import net.sf.openrocket.util.Transformation; public class RailButtonCalcTest { protected final double EPSILON = 0.0001; diff --git a/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java index b32b5e786..d980cfd25 100644 --- a/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java @@ -1,6 +1,8 @@ package net.sf.openrocket.aerodynamics; import static org.junit.Assert.assertEquals; + +import net.sf.openrocket.logging.WarningSet; import org.junit.BeforeClass; import org.junit.Test; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/BodyTubeHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/BodyTubeHandlerTest.java index b8830bb1b..c3861b6df 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/BodyTubeHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/BodyTubeHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/FinSetHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/FinSetHandlerTest.java index 297528289..19eb0b16b 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/FinSetHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/FinSetHandlerTest.java @@ -21,7 +21,7 @@ import com.google.inject.Module; import com.google.inject.util.Modules; import net.sf.openrocket.ServicesForTesting; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.plugin.PluginModule; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandlerTest.java index 0a2bf9e03..1d1203f16 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/InnerBodyTubeHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/LaunchLugHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/LaunchLugHandlerTest.java index de79aea33..98079e1a5 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/LaunchLugHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/LaunchLugHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/MassObjectHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/MassObjectHandlerTest.java index 217026db6..f9d82a4aa 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/MassObjectHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/MassObjectHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/NoseConeHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/NoseConeHandlerTest.java index 0e4a449bd..1a9b5997e 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/NoseConeHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/NoseConeHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimNoseConeCode; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/ParachuteHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/ParachuteHandlerTest.java index c01655955..1ab81e638 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/ParachuteHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/ParachuteHandlerTest.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.junit.Assert; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/RingHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/RingHandlerTest.java index 11e87e61d..ac0642c47 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/RingHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/RingHandlerTest.java @@ -8,7 +8,7 @@ import java.util.HashMap; import org.junit.Assert; import org.junit.Test; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/StreamerHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/StreamerHandlerTest.java index cb48db84b..1482d98d4 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/StreamerHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/StreamerHandlerTest.java @@ -8,7 +8,7 @@ import java.util.HashMap; import org.junit.Assert; import org.junit.Test; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimDensityType; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/TransitionHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/TransitionHandlerTest.java index 4e5c069a7..59f81e699 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/TransitionHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/TransitionHandlerTest.java @@ -3,7 +3,7 @@ */ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.file.rocksim.RockSimNoseConeCode; import net.sf.openrocket.file.simplesax.PlainTextHandler; diff --git a/core/test/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandlerTest.java b/core/test/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandlerTest.java index 63cffbf17..45e4c6fa2 100644 --- a/core/test/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandlerTest.java +++ b/core/test/net/sf/openrocket/file/rocksim/importt/TubeFinSetHandlerTest.java @@ -1,6 +1,6 @@ package net.sf.openrocket.file.rocksim.importt; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.file.rocksim.RockSimCommonConstants; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.ExternalComponent; diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index b695423c7..59638c8e4 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -15,7 +15,7 @@ import org.junit.Test; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc; import net.sf.openrocket.material.Material; import net.sf.openrocket.material.Material.Type; diff --git a/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java b/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java index 71bfa9ab5..503994a6d 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java @@ -3,7 +3,7 @@ package net.sf.openrocket.rocketcomponent; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.TestRockets; diff --git a/core/test/net/sf/openrocket/simulation/TestFlightData.java b/core/test/net/sf/openrocket/simulation/TestFlightData.java index d9620d529..66889dc76 100644 --- a/core/test/net/sf/openrocket/simulation/TestFlightData.java +++ b/core/test/net/sf/openrocket/simulation/TestFlightData.java @@ -6,7 +6,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; /** * Tests the FlightData object. diff --git a/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java b/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java index 7aeef0a25..7eb1efb6b 100644 --- a/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java +++ b/swing/src/net/sf/openrocket/file/SimulationTableCSVExport.java @@ -12,8 +12,8 @@ import javax.swing.JTable; import net.sf.openrocket.util.StringUtils; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.ColumnTableModel; diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index d05c0b039..ddb80fffb 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -11,7 +11,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; import java.util.ArrayList; import java.util.EventObject; import java.util.List; @@ -40,8 +39,8 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.ColumnTable; import net.sf.openrocket.gui.adaptors.ColumnTableModel; diff --git a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java index bb6a89a1b..14d122919 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java @@ -7,8 +7,8 @@ import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JScrollPane; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; @SuppressWarnings("serial") public class WarningDialog extends JDialog { diff --git a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java index 0e185251d..64c120c19 100644 --- a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java +++ b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java @@ -11,8 +11,8 @@ import java.awt.Rectangle; import java.awt.font.GlyphVector; import java.awt.geom.Rectangle2D; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.simulation.FlightData; diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 2cec966ea..f31f597f1 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -49,7 +49,7 @@ import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.appearance.DecalImage; import net.sf.openrocket.arch.SystemInfo; import net.sf.openrocket.document.OpenRocketDocument; diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 17336d761..88efc984f 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -48,8 +48,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.aerodynamics.Warning; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.Simulation.Status; diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index bc9ebf44e..1b4ac81a0 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -35,7 +35,7 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; -import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.events.SimulationChangeEvent; diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java index d9625cb17..cdb3706a4 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import javax.swing.JOptionPane; -import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.logging.Warning; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.dialogs.DetailDialog; import net.sf.openrocket.l10n.Translator; From 2bc93530bcebb986fca9b7a0be1b74de89305334 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 26 Mar 2023 23:59:36 +0200 Subject: [PATCH 06/76] Refactor WarningSet in general classes --- .../net/sf/openrocket/logging/Message.java | 43 +++++ .../net/sf/openrocket/logging/MessageSet.java | 123 ++++++++++++++ .../net/sf/openrocket/logging/Warning.java | 56 ++----- .../net/sf/openrocket/logging/WarningSet.java | 153 ++++-------------- 4 files changed, 207 insertions(+), 168 deletions(-) create mode 100644 core/src/net/sf/openrocket/logging/Message.java create mode 100644 core/src/net/sf/openrocket/logging/MessageSet.java diff --git a/core/src/net/sf/openrocket/logging/Message.java b/core/src/net/sf/openrocket/logging/Message.java new file mode 100644 index 000000000..9b813c1a8 --- /dev/null +++ b/core/src/net/sf/openrocket/logging/Message.java @@ -0,0 +1,43 @@ +package net.sf.openrocket.logging; + +/** + * Baseclass for logging messages (warnings, errors...) + */ +public abstract class Message { + /** + * @return a Message with the specific text. + */ + public static Message fromString(String text) { + return new Warning.Other(text); + } + + /** + * Return true if the other warning should replace + * this message. The method should return true if the other + * indicates a "worse" condition than the current warning. + * + * @param other the message to compare to + * @return whether this message should be replaced + */ + public abstract boolean replaceBy(Message other); + + + /** + * Two Messages are by default considered equal if they are of + * the same class. Therefore only one instance of a particular message type + * is stored in a {@link MessageSet}. Subclasses may override this method for + * more specific functionality. + */ + @Override + public boolean equals(Object o) { + return o != null && (o.getClass() == this.getClass()); + } + + /** + * A hashCode method compatible with the equals method. + */ + @Override + public int hashCode() { + return this.getClass().hashCode(); + } +} diff --git a/core/src/net/sf/openrocket/logging/MessageSet.java b/core/src/net/sf/openrocket/logging/MessageSet.java new file mode 100644 index 000000000..98642d1aa --- /dev/null +++ b/core/src/net/sf/openrocket/logging/MessageSet.java @@ -0,0 +1,123 @@ +package net.sf.openrocket.logging; + +import net.sf.openrocket.util.ArrayList; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.Monitorable; +import net.sf.openrocket.util.Mutable; + +import java.util.AbstractSet; +import java.util.Iterator; + +/** + * A set that contains multiple Messages. When adding a + * {@link Message} to this set, the contents is checked for a message of the + * same type. If one is found, then the message left in the set is determined + * by the method {@link Message#replaceBy(Message)}. + *

+ * A MessageSet can be made immutable by calling {@link #immute()}. + * + * @author Sampo Niskanen + */ +public abstract class MessageSet extends AbstractSet implements Cloneable, Monitorable { + /** the actual array of messages */ + protected ArrayList messages = new ArrayList<>(); + + protected Mutable mutable = new Mutable(); + private int modID = 0; + + /** + * Add a Message to the set. If a message of the same type + * exists in the set, the message that is left in the set is defined by the + * method {@link Message#replaceBy(Message)}. + * + * @throws IllegalStateException if this message set has been made immutable. + */ + @Override + public boolean add(E m) { + mutable.check(); + + modID++; + int index = messages.indexOf(m); + + if (index < 0) { + messages.add(m); + return false; + } + + E old = messages.get(index); + if (old.replaceBy(m)) { + messages.set(index, m); + } + + return true; + } + + /** + * Add a Message with the specified text to the set. + * + * @param s the message text. + * @throws IllegalStateException if this message set has been made immutable. + */ + public abstract boolean add(String s); + + /** + * Add a Message of the specified type with the specified discriminator to the + * set. + * @param m the message + * @param d the extra discriminator + * + */ + public boolean add (E m, String d) { + return this.add(m.toString() + ": \"" + d + "\""); + } + + @Override + public Iterator iterator() { + final Iterator iterator = messages.iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public E next() { + return iterator.next(); + } + + @Override + public void remove() { + mutable.check(); + iterator.remove(); + } + }; + } + + @Override + public int size() { + return messages.size(); + } + + + public void immute() { + mutable.immute(); + } + + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + + for (Message m : messages) { + if (s.length() > 0) + s.append(","); + s.append(m.toString()); + } + return "Messages[" + s + "]"; + } + + @Override + public int getModID() { + return modID; + } +} diff --git a/core/src/net/sf/openrocket/logging/Warning.java b/core/src/net/sf/openrocket/logging/Warning.java index 8d053758c..9d1bc0421 100644 --- a/core/src/net/sf/openrocket/logging/Warning.java +++ b/core/src/net/sf/openrocket/logging/Warning.java @@ -6,50 +6,22 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.unit.UnitGroup; -public abstract class Warning { +/** + * A warning message wrapper. + */ +public abstract class Warning extends Message { /** support to multiple languages warning */ private static final Translator trans = Application.getTranslator(); - + /** * @return a Warning with the specific text. */ public static Warning fromString(String text) { return new Warning.Other(text); } - - /** - * Return true if the other warning should replace - * this warning. The method should return true if the other - * warning indicates a "worse" condition than the current warning. - * - * @param other the warning to compare to - * @return whether this warning should be replaced - */ - public abstract boolean replaceBy(Warning other); - - - /** - * Two Warnings are by default considered equal if they are of - * the same class. Therefore only one instance of a particular warning type - * is stored in a {@link WarningSet}. Subclasses may override this method for - * more specific functionality. - */ - @Override - public boolean equals(Object o) { - return o != null && (o.getClass() == this.getClass()); - } - - /** - * A hashCode method compatible with the equals method. - */ - @Override - public int hashCode() { - return this.getClass().hashCode(); - } - - - + + ///////////// Specific warning classes ///////////// @@ -80,9 +52,9 @@ public abstract class Warning { return (trans.get("Warning.LargeAOA.str2") + UnitGroup.UNITS_ANGLE.getDefaultUnit().toString(aoa) + ")."); } - + @Override - public boolean replaceBy(Warning other) { + public boolean replaceBy(Message other) { if (!(other instanceof LargeAOA)) return false; @@ -119,7 +91,7 @@ public abstract class Warning { } @Override - public boolean replaceBy(Warning other) { + public boolean replaceBy(Message other) { return false; } } @@ -134,7 +106,7 @@ public abstract class Warning { /** * Sole constructor. The argument is an event which has occurred after landing * - * @param event the event that caused this warning + * @param _event the event that caused this warning */ public EventAfterLanding(FlightEvent _event) { this.event = _event; @@ -154,7 +126,7 @@ public abstract class Warning { } @Override - public boolean replaceBy(Warning other) { + public boolean replaceBy(Message other) { return false; } } @@ -249,7 +221,7 @@ public abstract class Warning { @Override - public boolean replaceBy(Warning other) { + public boolean replaceBy(Message other) { return false; } @@ -349,7 +321,7 @@ public abstract class Warning { } @Override - public boolean replaceBy(Warning other) { + public boolean replaceBy(Message other) { return false; } } diff --git a/core/src/net/sf/openrocket/logging/WarningSet.java b/core/src/net/sf/openrocket/logging/WarningSet.java index 2c248786b..2220904ba 100644 --- a/core/src/net/sf/openrocket/logging/WarningSet.java +++ b/core/src/net/sf/openrocket/logging/WarningSet.java @@ -12,136 +12,37 @@ import net.sf.openrocket.util.Mutable; * A set that contains multiple Warnings. When adding a * {@link Warning} to this set, the contents is checked for a warning of the * same type. If one is found, then the warning left in the set is determined - * by the method {@link Warning#replaceBy(Warning)}. + * by the method {@link Warning#replaceBy(Message)}. *

* A WarningSet can be made immutable by calling {@link #immute()}. * * @author Sampo Niskanen */ -public class WarningSet extends AbstractSet implements Cloneable, Monitorable { - - /** the actual array of warnings*/ - private ArrayList warnings = new ArrayList(); - - private Mutable mutable = new Mutable(); - - private int modID = 0; - - /** - * Add a Warning to the set. If a warning of the same type - * exists in the set, the warning that is left in the set is defined by the - * method {@link Warning#replaceBy(Warning)}. - * - * @throws IllegalStateException if this warning set has been made immutable. - */ - @Override - public boolean add(Warning w) { - mutable.check(); - - modID++; - int index = warnings.indexOf(w); - - if (index < 0) { - warnings.add(w); - return false; - } - - Warning old = warnings.get(index); - if (old.replaceBy(w)) { - warnings.set(index, w); - } - - return true; - } - - /** - * Add a Warning with the specified text to the set. The Warning object - * is created using the {@link Warning#fromString(String)} method. If a warning of the - * same type exists in the set, the warning that is left in the set is defined by the - * method {@link Warning#replaceBy(Warning)}. - * - * @param s the warning text. - * @throws IllegalStateException if this warning set has been made immutable. - */ - public boolean add(String s) { - mutable.check(); - return add(Warning.fromString(s)); - } +public class WarningSet extends MessageSet { + /** + * Add a Warning with the specified text to the set. The Warning object + * is created using the {@link Message#fromString(String)} method. If a warning of the + * same type exists in the set, the warning that is left in the set is defined by the + * method {@link Warning#replaceBy(Message)}. + * + * @param s the message text. + * @throws IllegalStateException if this message set has been made immutable. + */ + public boolean add(String s) { + mutable.check(); + return add(Warning.fromString(s)); + } - /** - * Add a Warning of the specified type with the specified discriminator to the - * set. - * @param w the warning - * @param d the extra discriminator - * - */ - public boolean add (Warning w, String d) { - return this.add(w.toString() + ": \"" + d + "\""); - } - - @Override - public Iterator iterator() { - final Iterator iterator = warnings.iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Warning next() { - return iterator.next(); - } - - @Override - public void remove() { - mutable.check(); - iterator.remove(); - } - - }; - } - - @Override - public int size() { - return warnings.size(); - } - - - public void immute() { - mutable.immute(); - } - - - @Override - public WarningSet clone() { - try { - - WarningSet newSet = (WarningSet) super.clone(); - newSet.warnings = this.warnings.clone(); - newSet.mutable = this.mutable.clone(); - return newSet; - - } catch (CloneNotSupportedException e) { - throw new BugException("CloneNotSupportedException occurred, report bug!", e); - } - } - - - @Override - public String toString() { - String s = ""; - - for (Warning w : warnings) { - if (s.length() > 0) - s = s + ","; - s += w.toString(); - } - return "WarningSet[" + s + "]"; - } - - @Override - public int getModID() { - return modID; - } + @Override + public WarningSet clone() { + try { + WarningSet newSet = (WarningSet) super.clone(); + newSet.messages = this.messages.clone(); + newSet.mutable = this.mutable.clone(); + return newSet; + + } catch (CloneNotSupportedException e) { + throw new BugException("CloneNotSupportedException occurred, report bug!", e); + } + } } From c5a2fc44df90d93f28e85fd6612f84dcee5e5aed Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 27 Mar 2023 00:05:03 +0200 Subject: [PATCH 07/76] Create an error message & set --- core/src/net/sf/openrocket/logging/Error.java | 59 +++++++++++++++++++ .../net/sf/openrocket/logging/ErrorSet.java | 32 ++++++++++ 2 files changed, 91 insertions(+) create mode 100644 core/src/net/sf/openrocket/logging/Error.java create mode 100644 core/src/net/sf/openrocket/logging/ErrorSet.java diff --git a/core/src/net/sf/openrocket/logging/Error.java b/core/src/net/sf/openrocket/logging/Error.java new file mode 100644 index 000000000..69ba76dd6 --- /dev/null +++ b/core/src/net/sf/openrocket/logging/Error.java @@ -0,0 +1,59 @@ +package net.sf.openrocket.logging; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; + +/** + * An error message wrapper. + */ +public abstract class Error extends Message { + private static final Translator trans = Application.getTranslator(); + + /** + * @return an Error with the specific text. + */ + public static Error fromString(String text) { + return new Error.Other(text); + } + + + ///////////// Specific Error classes ///////////// + + + /** + * An unspecified error type. This error type holds a String + * describing it. Two errors of this type are considered equal if the strings + * are identical. + */ + public static class Other extends Error { + private final String description; + + public Other(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Other)) + return false; + + Other o = (Other) other; + return (o.description.equals(this.description)); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + @Override + public boolean replaceBy(Message other) { + return false; + } + } +} diff --git a/core/src/net/sf/openrocket/logging/ErrorSet.java b/core/src/net/sf/openrocket/logging/ErrorSet.java new file mode 100644 index 000000000..76ea8977a --- /dev/null +++ b/core/src/net/sf/openrocket/logging/ErrorSet.java @@ -0,0 +1,32 @@ +package net.sf.openrocket.logging; + +import net.sf.openrocket.util.BugException; + +public class ErrorSet extends MessageSet { + /** + * Add an Error with the specified text to the set. The Error object + * is created using the {@link Error#fromString(String)} method. If an error of the + * same type exists in the set, the error that is left in the set is defined by the + * method {@link Error#replaceBy(Message)}. + * + * @param s the message text. + * @throws IllegalStateException if this message set has been made immutable. + */ + public boolean add(String s) { + mutable.check(); + return add(Error.fromString(s)); + } + + @Override + public ErrorSet clone() { + try { + ErrorSet newSet = (ErrorSet) super.clone(); + newSet.messages = this.messages.clone(); + newSet.mutable = this.mutable.clone(); + return newSet; + + } catch (CloneNotSupportedException e) { + throw new BugException("CloneNotSupportedException occurred, report bug!", e); + } + } +} From 9667466fdbb4a78fad951cd357aef1de2d6b89a6 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 27 Mar 2023 22:31:52 +0200 Subject: [PATCH 08/76] Add error & warning dialog for RASAero exporting --- core/resources/l10n/messages.properties | 5 + .../openrocket/file/GeneralRocketSaver.java | 32 ++++- .../net/sf/openrocket/file/RocketSaver.java | 13 +- .../file/openrocket/OpenRocketSaver.java | 4 +- .../file/rasaero/export/BasePartDTO.java | 12 +- .../file/rasaero/export/BodyTubeDTO.java | 22 ++- .../rasaero/export/BodyTubeDTOAdapter.java | 22 +-- .../file/rasaero/export/BoosterDTO.java | 41 +++++- .../file/rasaero/export/NoseConeDTO.java | 6 +- .../file/rasaero/export/RASAeroSaver.java | 26 ++-- .../file/rasaero/export/RocketDesignDTO.java | 54 +++++--- .../file/rasaero/export/TransitionDTO.java | 7 +- .../file/rocksim/export/RockSimSaver.java | 4 +- .../net/sf/openrocket/util/TestRockets.java | 4 +- .../file/openrocket/OpenRocketSaverTest.java | 4 +- .../gui/dialogs/ErrorWarningDialog.java | 128 ++++++++++++++++++ .../openrocket/gui/dialogs/WarningDialog.java | 2 +- .../sf/openrocket/gui/main/BasicFrame.java | 29 +++- 18 files changed, 342 insertions(+), 73 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 3d8cff127..114860e9e 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -89,7 +89,12 @@ BasicFrame.dlg.title = Design not saved BasicFrame.StageName.Sustainer = Sustainer BasicFrame.WarningDialog.txt1 = The following problems were encountered while opening BasicFrame.WarningDialog.txt2 = Some design features may not have been loaded correctly. +BasicFrame.WarningDialog.saving.txt1 = The following problems were encountered while saving +BasicFrame.WarningDialog.saving.txt2 = Some design features may not have exported correctly. BasicFrame.WarningDialog.title = Warnings while opening file +BasicFrame.WarningDialog.saving.title = Warnings while opening file +BasicFrame.ErrorWarningDialog.txt1 = Please correct the errors. +BasicFrame.ErrorWarningDialog.saving.title = Errors/Warnings while saving file ! General error messages used in multiple contexts diff --git a/core/src/net/sf/openrocket/file/GeneralRocketSaver.java b/core/src/net/sf/openrocket/file/GeneralRocketSaver.java index a327e3be5..d28fcbc20 100644 --- a/core/src/net/sf/openrocket/file/GeneralRocketSaver.java +++ b/core/src/net/sf/openrocket/file/GeneralRocketSaver.java @@ -21,12 +21,16 @@ import net.sf.openrocket.document.StorageOptions.FileType; import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.file.rasaero.export.RASAeroSaver; import net.sf.openrocket.file.rocksim.export.RockSimSaver; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.InsideColorComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.DecalNotFoundException; import net.sf.openrocket.util.MathUtil; public class GeneralRocketSaver { + protected final WarningSet warnings = new WarningSet(); + protected final ErrorSet errors = new ErrorSet(); /** * Interface which can be implemented by the caller to receive progress information. @@ -234,16 +238,34 @@ public class GeneralRocketSaver { private void saveInternal(OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException { - + warnings.clear(); + errors.clear(); + if (options.getFileType() == FileType.ROCKSIM) { - new RockSimSaver().save(output, document, options); + new RockSimSaver().save(output, document, options, warnings, errors); } else if (options.getFileType() == FileType.RASAERO) { - new RASAeroSaver().save(output, document, options); + new RASAeroSaver().save(output, document, options, warnings, errors); } else { - new OpenRocketSaver().save(output, document, options); + new OpenRocketSaver().save(output, document, options, warnings, errors); } } - + + /** + * Return a list of warnings generated during the saving process. + * @return a list of warnings generated during the saving process + */ + public WarningSet getWarnings() { + return warnings; + } + + /** + * Return a list of errors generated during the saving process. + * @return a list of errors generated during the saving process + */ + public ErrorSet getErrors() { + return errors; + } + private static class ProgressOutputStream extends FilterOutputStream { private final long estimatedSize; diff --git a/core/src/net/sf/openrocket/file/RocketSaver.java b/core/src/net/sf/openrocket/file/RocketSaver.java index fa1d4b56b..7ef09e0c1 100644 --- a/core/src/net/sf/openrocket/file/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/RocketSaver.java @@ -3,23 +3,24 @@ package net.sf.openrocket.file; import java.io.IOException; import java.io.OutputStream; +import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; public abstract class RocketSaver { - protected final WarningSet warnings = new WarningSet(); - /** * Save the document to the specified output stream using the default storage options. * * @param dest the destination stream. * @param doc the document to save. + * @param warnings list to store save warnings to + * @param errors list to store save errors to * @throws IOException in case of an I/O error. */ - public final void save(OutputStream dest, OpenRocketDocument doc) throws IOException { - save(dest, doc, doc.getDefaultStorageOptions()); + public final void save(OutputStream dest, OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) throws IOException { + save(dest, doc, doc.getDefaultStorageOptions(), warnings, errors); } /** @@ -28,9 +29,11 @@ public abstract class RocketSaver { * @param dest the destination stream. * @param doc the document to save. * @param options the storage options. + * @param warnings list to store save warnings to + * @param errors list to store save errors to * @throws IOException in case of an I/O error. */ - public abstract void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException; + public abstract void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options, WarningSet warnings, ErrorSet errors) throws IOException; /** * Provide an estimate of the file size when saving the document with the diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 7cb022c44..77602523d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -8,6 +8,8 @@ import java.io.Writer; import java.util.*; import net.sf.openrocket.file.openrocket.savers.PhotoStudioSaver; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +62,7 @@ public class OpenRocketSaver extends RocketSaver { private Writer dest; @Override - public void save(OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException { + public void save(OutputStream output, OpenRocketDocument document, StorageOptions options, WarningSet warnings, ErrorSet errors) throws IOException { log.info("Saving .ork file"); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java index 42dad3103..f834d68c1 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.NoseCone; @@ -43,16 +45,24 @@ public class BasePartDTO { @XmlTransient private final RocketComponent component; + @XmlTransient + private final WarningSet warnings; + @XmlTransient + private final ErrorSet errors; /** * We need a default no-args constructor. */ public BasePartDTO() { this.component = null; + this.warnings = null; + this.errors = null; } - protected BasePartDTO(RocketComponent component) throws RASAeroExportException { + protected BasePartDTO(RocketComponent component, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { this.component = component; + this.warnings = warnings; + this.errors = errors; if (component instanceof BodyTube) { setPartType(RASAeroCommonConstants.BODY_TUBE); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java index a12f7f42b..8b5fc6ce9 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; import javax.xml.bind.annotation.XmlAccessType; @@ -9,6 +11,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -49,14 +52,25 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { @XmlElementRef(name = RASAeroCommonConstants.FIN, type = FinDTO.class) private FinDTO fin; + + @XmlTransient + private final WarningSet warnings; + @XmlTransient + private final ErrorSet errors; + /** * We need a default no-args constructor. */ - public BodyTubeDTO() { } + public BodyTubeDTO() { + this.warnings = null; + this.errors = null; + } - public BodyTubeDTO(BodyTube bodyTube) throws RASAeroExportException { - super(bodyTube); - applyBodyTubeSettings(bodyTube); + public BodyTubeDTO(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + super(bodyTube, warnings, errors); + this.warnings = warnings; + this.errors = errors; + applyBodyTubeSettings(bodyTube, warnings, errors); } public Double getLaunchLugDiameter() { diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index 3c7d84e41..00b5eb954 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -1,6 +1,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.RailButton; @@ -10,17 +12,17 @@ import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; public interface BodyTubeDTOAdapter { - default void applyBodyTubeSettings(BodyTube bodyTube) throws RASAeroExportException { + default void applyBodyTubeSettings(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { for (RocketComponent child : bodyTube.getChildren()) { if (child instanceof TrapezoidFinSet) { setFin(new FinDTO((TrapezoidFinSet) child)); } else if (child instanceof LaunchLug) { if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - //TODO: warning.add(String.format("Already added a rail button, ignoring launch lug '%s'", child.getName()); + warnings.add(String.format("Already added a rail button, ignoring launch lug '%s'", child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - //TODO: warning.add(String.format("Already added a launch shoe, ignoring launch lug '%s'", child.getName()); + warnings.add(String.format("Already added a launch shoe, ignoring launch lug '%s'", child.getName())); continue; } @@ -29,17 +31,18 @@ public interface BodyTubeDTOAdapter { if (lug.getInstanceCount() == 2) { setLaunchLugLength(lug.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else { - //TODO:warnings.add(String.format("Instance count of '%s' not set to 2, defaulting to 2 and adjusting - // launch lug length accordingly.", lug.getName()") + warnings.add(String.format( + "Instance count of '%s' not set to 2, defaulting to 2 and adjusting launch lug length accordingly.", + lug.getName())); setLaunchLugLength(lug.getLength() * lug.getInstanceCount() / 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } } else if (child instanceof RailButton) { if (!MathUtil.equals(getLaunchLugDiameter(), 0) || !MathUtil.equals(getLaunchLugLength(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - // TODO: warning.add(String.format("Already added a launch lug, ignoring rail button '%s'", child.getName())); + warnings.add(String.format("Already added a launch lug, ignoring rail button '%s'", child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - // TODO: warning.add(String.format("Already added a launch shoe, ignoring rail button '%s'", child.getName())); + warnings.add(String.format("Already added a launch shoe, ignoring rail button '%s'", child.getName())); continue; } @@ -48,10 +51,11 @@ public interface BodyTubeDTOAdapter { setRailGuideHeight(button.getTotalHeight() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); if (button.getInstanceCount() != 2) { - //TODO: warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2", button.getName(), button.getInstanceCount())); + warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2", + button.getName(), button.getInstanceCount())); } } else { - // TODO: warnings.add(String.format("Unsupported component '%s', ignoring.", child.getComponentName())); + warnings.add(String.format("Unsupported component '%s', ignoring.", child.getComponentName())); } } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index b606c4e76..cccdce560 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.NoseCone; @@ -13,6 +15,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -74,12 +77,28 @@ public class BoosterDTO implements BodyTubeDTOAdapter { private FinDTO fin; + @XmlTransient + private final RocketComponent component; + @XmlTransient + private final WarningSet warnings; + @XmlTransient + private final ErrorSet errors; + + /** * We need a default, no-args constructor. */ - public BoosterDTO() { } + public BoosterDTO() { + this.component = null; + this.warnings = null; + this.errors = null; + } + + protected BoosterDTO(Rocket rocket, AxialStage stage, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + this.component = stage; + this.warnings = warnings; + this.errors = errors; - protected BoosterDTO(Rocket rocket, AxialStage stage) throws RASAeroExportException { int stageNr = rocket.getChildPosition(stage); // Use this instead of stage.getStageNumber() in case there are parallel stages in the design if (stageNr != 1 && stageNr != 2) { throw new RASAeroExportException(String.format("Invalid stage number '%d' for booster stage '%s'", stageNr, stage.getName())); @@ -129,7 +148,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { firstTube = (BodyTube) stage.getChild(0); } - applyBodyTubeSettings(firstTube); + applyBodyTubeSettings(firstTube, warnings, errors); TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); if (finSet == null) { @@ -144,6 +163,8 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setLocation(firstChild.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(firstTube.getColor())); + + // TODO: parse children for body tubes , transtitions etc. } private TrapezoidFinSet getFinSetFromBodyTube(BodyTube bodyTube) { @@ -168,6 +189,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { } public void setLength(Double length) { + if (MathUtil.equals(length, 0)) { + errors.add(String.format("Length of '%s' must be greater than 0", component.getName())); + return; + } this.length = length; } @@ -175,7 +200,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { return diameter; } - public void setDiameter(Double diameter) { + public void setDiameter(Double diameter) throws RASAeroExportException { + if (MathUtil.equals(diameter, 0)) { + throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0", component.getName())); + } this.diameter = diameter; } @@ -183,7 +211,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { return insideDiameter; } - public void setInsideDiameter(Double insideDiameter) { + public void setInsideDiameter(Double insideDiameter) throws RASAeroExportException { + if (MathUtil.equals(insideDiameter, 0)) { + throw new RASAeroExportException(String.format("Inside diameter of '%s' must be greater than 0", component.getName())); + } this.insideDiameter = insideDiameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java index c1fae0c47..cd1e94acd 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.NoseCone; import javax.xml.bind.annotation.XmlAccessType; @@ -31,8 +33,8 @@ public class NoseConeDTO extends BasePartDTO { public NoseConeDTO() { } - public NoseConeDTO(NoseCone noseCone) throws RASAeroExportException { - super(noseCone); + public NoseConeDTO(NoseCone noseCone, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + super(noseCone, warnings, errors); NoseConeShapeSettings shapeSettings = RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SHAPE(noseCone.getShapeType(), noseCone.getShapeParameter()); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java index 45a717611..110cac778 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -3,12 +3,13 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; import net.sf.openrocket.file.RocketSaver; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.Rocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.BufferedWriter; import java.io.IOException; @@ -35,7 +36,7 @@ public class RASAeroSaver extends RocketSaver { * @param doc the OR design * @return RASAero-compliant XML */ - public String marshalToRASAero(OpenRocketDocument doc) { + public String marshalToRASAero(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) { try { JAXBContext binder = JAXBContext.newInstance(RASAeroDocumentDTO.class); Marshaller marshaller = binder.createMarshaller(); @@ -43,10 +44,10 @@ public class RASAeroSaver extends RocketSaver { marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); StringWriter sw = new StringWriter(); - marshaller.marshal(toRASAeroDocumentDTO(doc), sw); + marshaller.marshal(toRASAeroDocumentDTO(doc, warnings, errors), sw); return sw.toString(); } catch (RASAeroExportException e) { - throw new RuntimeException(e); + errors.add(e.getMessage()); } catch (Exception e) { log.error("Could not marshall a design to RASAero format. " + e.getMessage()); } @@ -55,28 +56,30 @@ public class RASAeroSaver extends RocketSaver { } @Override - public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException { + public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options, WarningSet warnings, ErrorSet errors) throws IOException { log.info("Saving .CDX1 file"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, StandardCharsets.UTF_8)); - writer.write(marshalToRASAero(doc)); + writer.write(marshalToRASAero(doc, warnings, errors)); writer.flush(); } @Override public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) { - return marshalToRASAero(doc).length(); + return marshalToRASAero(doc, new WarningSet(), new ErrorSet()).length(); } /** * Root conversion method. It iterates over all subcomponents. * * @param doc the OR design + * @param warnings list to add export warnings to + * @param errors list to add export errors to * @return a corresponding RASAero representation */ - private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc) throws RASAeroExportException { + private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); - rad.setDesign(toRocketDesignDTO(doc.getRocket())); + rad.setDesign(toRocketDesignDTO(doc.getRocket(), warnings, errors)); return rad; } @@ -86,8 +89,7 @@ public class RASAeroSaver extends RocketSaver { * @param rocket the OR rocket to export the components from * @return the RASAero rocket design */ - private RocketDesignDTO toRocketDesignDTO(Rocket rocket) throws RASAeroExportException { - RocketDesignDTO result = new RocketDesignDTO(rocket); - return result; + private RocketDesignDTO toRocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + return new RocketDesignDTO(rocket, warnings, errors); } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index 4453018e9..887b5d3f4 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -3,6 +3,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.NoseCone; @@ -65,10 +67,10 @@ public class RocketDesignDTO { @XmlElement(name = RASAeroCommonConstants.COMMENTS) private String comments = ""; - public RocketDesignDTO(Rocket rocket) throws RASAeroExportException { + public RocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { setComments(rocket.getComment()); if (rocket.getChildCount() > 3) { - throw new RASAeroExportException("Rocket should have no more then 3 stages (excl. boosters) in total"); + warnings.add("Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stages."); } setUseBooster1(rocket.getStageCount() >= 2); setUseBooster2(rocket.getStageCount() == 3); @@ -77,30 +79,40 @@ public class RocketDesignDTO { // Export components from sustainer for (int i = 0; i < sustainer.getChildCount(); i++) { - RocketComponent component = sustainer.getChild(i); - if (i == 0 && !(component instanceof NoseCone)) { - throw new RASAeroExportException("First component of the sustainer must be a nose cone"); - } else if (i == 1 && !(component instanceof BodyTube)) { - throw new RASAeroExportException("Second component of the sustainer must be a body tube"); - } - if (component instanceof BodyTube) { - addExternalPart(new BodyTubeDTO((BodyTube) component)); - } else if (component instanceof NoseCone) { - if (i != 0) { - throw new RASAeroExportException("A nose cone can only be the first component of the rocket"); + try { + RocketComponent component = sustainer.getChild(i); + if (i == 0 && !(component instanceof NoseCone)) { + errors.add("First component of the sustainer must be a nose cone"); + return; + } else if (i == 1 && !(component instanceof BodyTube)) { + errors.add("Second component of the sustainer must be a body tube"); + return; } - addExternalPart(new NoseConeDTO((NoseCone) component)); - // Set the global surface finish to that of the first nose cone - setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish())); - } - else if (component instanceof Transition) { - addExternalPart(new TransitionDTO((Transition) component)); + if (component instanceof BodyTube) { + addExternalPart(new BodyTubeDTO((BodyTube) component, warnings, errors)); + } else if (component instanceof NoseCone) { + if (i != 0) { + errors.add("A nose cone can only be the first component of the rocket"); + return; + } + addExternalPart(new NoseConeDTO((NoseCone) component, warnings, errors)); + // Set the global surface finish to that of the first nose cone + setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish())); + } else if (component instanceof Transition) { + addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); + } + } catch (RASAeroExportException e) { + errors.add(e.getMessage()); } } // Export components from other stages - for (int i = 1; i < rocket.getChildCount(); i++) { - addBooster(new BoosterDTO(rocket, (AxialStage) rocket.getChild(i))); + for (int i = 1; i < Math.min(rocket.getChildCount(), 3); i++) { + try { + addBooster(new BoosterDTO(rocket, (AxialStage) rocket.getChild(i), warnings, errors)); + } catch (RASAeroExportException e) { + errors.add(e.getMessage()); + } } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java index 6259876cb..b0756df94 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -2,7 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -31,8 +32,8 @@ public class TransitionDTO extends BasePartDTO { public TransitionDTO() { } - public TransitionDTO(Transition transition) throws RASAeroExportException { - super(transition); + public TransitionDTO(Transition transition, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + super(transition, warnings, errors); if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { throw new RASAeroExportException("RASAero only supports conical transitions"); diff --git a/core/src/net/sf/openrocket/file/rocksim/export/RockSimSaver.java b/core/src/net/sf/openrocket/file/rocksim/export/RockSimSaver.java index 002d85aac..593c8657d 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/RockSimSaver.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/RockSimSaver.java @@ -10,6 +10,8 @@ import java.nio.charset.StandardCharsets; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,7 +60,7 @@ public class RockSimSaver extends RocketSaver { } @Override - public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException { + public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options, WarningSet warnings, ErrorSet errors) throws IOException { log.info("Saving .rkt file"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, StandardCharsets.UTF_8)); diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 2a1c9397e..a6e5fd7ee 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -11,6 +11,8 @@ import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.OpenRocketDocumentFactory; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.openrocket.OpenRocketSaver; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.material.Material; import net.sf.openrocket.material.Material.Type; import net.sf.openrocket.motor.Manufacturer; @@ -1830,7 +1832,7 @@ public class TestRockets { OpenRocketSaver saver = new OpenRocketSaver(); try { FileOutputStream str = new FileOutputStream(filename); - saver.save(str, doc, null); + saver.save(str, doc, null, new WarningSet(), new ErrorSet()); } catch (Exception e) { System.err.println("exception " + e); diff --git a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java index 6c613ee32..0eae42619 100644 --- a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java +++ b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java @@ -29,6 +29,8 @@ import net.sf.openrocket.file.RocketLoadException; import net.sf.openrocket.file.motor.GeneralMotorLoader; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; @@ -363,7 +365,7 @@ public class OpenRocketSaverTest { try { file = File.createTempFile( TMP_DIR.getName(), ".ork"); out = new FileOutputStream(file); - this.saver.save(out, rocketDoc, options); + this.saver.save(out, rocketDoc, options, new WarningSet(), new ErrorSet()); } catch (FileNotFoundException e) { fail("FileNotFound saving temp file in: " + TMP_DIR.getName() + ": " + e.getMessage()); } catch (IOException e) { diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java new file mode 100644 index 000000000..f1b0834b7 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java @@ -0,0 +1,128 @@ +package net.sf.openrocket.gui.dialogs; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.components.StyledLabel; +import net.sf.openrocket.logging.Error; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.Warning; +import net.sf.openrocket.logging.WarningSet; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JViewport; +import javax.swing.ListSelectionModel; +import java.awt.Color; +import java.awt.Component; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * A message dialog displaying errors and warnings. + */ +@SuppressWarnings("serial") +public abstract class ErrorWarningDialog { + public static void showErrorsAndWarnings(Component parent, Object message, String title, ErrorSet errors, WarningSet warnings) { + JPanel content = new JPanel(new MigLayout("ins 0, fillx")); + + StyledLabel label = new StyledLabel("Errors"); + label.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + content.add(label, "wrap, gaptop 15lp"); + + Error[] e = errors.toArray(new Error[0]); + final JList errorList = new JList<>(e); + errorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + errorList.setCellRenderer(new ErrorListCellRenderer()); + JScrollPane errorPane = new JScrollPane(errorList); + content.add(errorPane, "wrap, growx"); + + // Deselect items if clicked on blank region + errorList.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int selectedIndex = errorList.locationToIndex(e.getPoint()); + if (selectedIndex < 0 || !errorList.getCellBounds(0, errorList.getLastVisibleIndex()).contains(e.getPoint())) { + errorList.clearSelection(); + } + } + }); + + content.add(new JSeparator(JSeparator.HORIZONTAL), "wrap"); + + content.add(new JLabel("Warnings:"), "wrap"); + + Warning[] w = warnings.toArray(new Warning[0]); + final JList warningList = new JList<>(w); + warningList.setCellRenderer(new WarningListCellRenderer()); + JScrollPane warningPane = new JScrollPane(warningList); + content.add(warningPane, "wrap, growx"); + + // Deselect items if clicked on blank region + warningList.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int selectedIndex = warningList.locationToIndex(e.getPoint()); + if (selectedIndex < 0 || !warningList.getCellBounds(0, warningList.getLastVisibleIndex()).contains(e.getPoint())) { + warningList.clearSelection(); + } + } + }); + + JOptionPane.showMessageDialog(parent, new Object[] { message, content }, + title, JOptionPane.WARNING_MESSAGE); + + } + + private static class ErrorListCellRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + // Alternating row colors + if (!isSelected) { + if (index % 2 == 0) { + label.setBackground(Color.WHITE); + } else { + label.setBackground(new Color(245, 245, 245)); + } + } + // Text color + if (isSelected) { + label.setForeground(Color.WHITE); + } else { + label.setForeground(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + } + return label; + } + } + + private static class WarningListCellRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + // Alternating row colors + if (!isSelected) { + if (index % 2 == 0) { + label.setBackground(Color.WHITE); + } else { + label.setBackground(new Color(245, 245, 245)); + } + } + // Text color + if (isSelected) { + label.setForeground(Color.WHITE); + } else { + label.setForeground(Color.BLACK); + } + return label; + } + } +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java index 14d122919..8c8f72476 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java @@ -11,7 +11,7 @@ import net.sf.openrocket.logging.Warning; import net.sf.openrocket.logging.WarningSet; @SuppressWarnings("serial") -public class WarningDialog extends JDialog { +public abstract class WarningDialog { public static void showWarnings(Component parent, Object message, String title, WarningSet warnings) { diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index f31f597f1..7a23aaf96 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -49,6 +49,8 @@ import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.dialogs.ErrorWarningDialog; +import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.appearance.DecalImage; import net.sf.openrocket.arch.SystemInfo; @@ -1466,8 +1468,33 @@ public class BasicFrame extends JFrame { private boolean saveRASAeroFile(File file, StorageOptions options) { try { ROCKET_SAVER.save(file, document, options); + + WarningSet warnings = ROCKET_SAVER.getWarnings(); + ErrorSet errors = ROCKET_SAVER.getErrors(); + + if (errors.isEmpty()) { + WarningDialog.showWarnings(this, + new Object[]{ + // // The following problems were encountered while saving + trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.", + // // Some design features may not have been exported correctly. + trans.get("BasicFrame.WarningDialog.saving.txt2") + }, + // // Warnings while opening file + trans.get("BasicFrame.WarningDialog.saving.title"), warnings); + } else { + ErrorWarningDialog.showErrorsAndWarnings(this, + new Object[]{ + // // The following problems were encountered while saving + trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.", + // // Please correct the errors. + trans.get("BasicFrame.ErrorWarningDialog.txt1") + }, + // // Errors/Warnings while saving file + trans.get("BasicFrame.ErrorWarningDialog.saving.title"), errors, warnings); + } // Do not update the save state of the document. - return true; + return errors.isEmpty(); } catch (IOException e) { return false; } catch (DecalNotFoundException decex) { From de8ab74b2d11f0568462f8c23b65f1af5ba36255 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 27 Mar 2023 22:39:18 +0200 Subject: [PATCH 09/76] Add warning for extra components in boosters --- .../file/rasaero/export/BoosterDTO.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index cccdce560..541094e3d 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -14,6 +14,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @@ -23,8 +24,11 @@ import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.MathUtil; +import java.util.List; + @XmlRootElement(name = RASAeroCommonConstants.BOOSTER) @XmlAccessorType(XmlAccessType.FIELD) public class BoosterDTO implements BodyTubeDTOAdapter { @@ -144,8 +148,17 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setShoulderLength(firstChild.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setInsideDiameter(transition.getForeRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + + if (stage.getChildCount() > 2) { + warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components", + stage.getName(), stage.getChildCount() - 2)); + } } else { firstTube = (BodyTube) stage.getChild(0); + if (stage.getChildCount() > 1) { + warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components", + stage.getName(), stage.getChildCount() - 1)); + } } applyBodyTubeSettings(firstTube, warnings, errors); @@ -163,8 +176,6 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setLocation(firstChild.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(firstTube.getColor())); - - // TODO: parse children for body tubes , transtitions etc. } private TrapezoidFinSet getFinSetFromBodyTube(BodyTube bodyTube) { From 8c9a04b74e005ed50edd7ebb695291e5b41fd73d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 27 Mar 2023 22:44:55 +0200 Subject: [PATCH 10/76] Add dots to warning/error texts --- .../file/rasaero/export/BasePartDTO.java | 10 +++---- .../file/rasaero/export/BodyTubeDTO.java | 10 +++---- .../rasaero/export/BodyTubeDTOAdapter.java | 10 +++---- .../file/rasaero/export/BoosterDTO.java | 30 ++++++++----------- .../file/rasaero/export/FinDTO.java | 2 +- .../file/rasaero/export/RocketDesignDTO.java | 6 ++-- .../file/rasaero/export/TransitionDTO.java | 6 ++-- 7 files changed, 35 insertions(+), 39 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java index f834d68c1..cc8bcf7f4 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java @@ -71,7 +71,7 @@ public class BasePartDTO { setPartType(RASAeroCommonConstants.NOSE_CONE); NoseCone noseCone = (NoseCone) component; if (noseCone.isFlipped()) { - throw new RASAeroExportException("Nose cone may not be flipped"); + throw new RASAeroExportException("Nose cone may not be flipped."); } setDiameter(((NoseCone) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (component instanceof Transition) { @@ -83,11 +83,11 @@ public class BasePartDTO { setPartType(RASAeroCommonConstants.BOOSTER); AxialStage stage = (AxialStage) component; if (stage.getChildCount() == 0 || !(stage.getChild(0) instanceof BodyTube)) { - throw new RASAeroExportException("First component of booster must be body tube"); + throw new RASAeroExportException("First component of booster must be body tube."); } setDiameter(stage.getBoundingRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else { - throw new RASAeroExportException("Unsupported component: " + component.getComponentName()); + throw new RASAeroExportException("Unsupported component: ." + component.getComponentName()); } setLength(component.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); @@ -109,7 +109,7 @@ public class BasePartDTO { public void setLength(Double length) throws RASAeroExportException { if (MathUtil.equals(length, 0)) { - throw new RASAeroExportException(String.format("Length of '%s' must be greater than 0", component.getName())); + throw new RASAeroExportException(String.format("Length of '%s' must be greater than 0.", component.getName())); } this.length = length; } @@ -120,7 +120,7 @@ public class BasePartDTO { public void setDiameter(Double diameter) throws RASAeroExportException { if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0", component.getName())); + throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0.", component.getName())); } this.diameter = diameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java index 8b5fc6ce9..668eaf13f 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java @@ -79,7 +79,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException { if (MathUtil.equals(launchLugDiameter, 0)) { - throw new RASAeroExportException("Launch lug diameter can not be 0"); + throw new RASAeroExportException("Launch lug diameter can not be 0."); } this.launchLugDiameter = launchLugDiameter; } @@ -90,7 +90,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException { if (MathUtil.equals(launchLugLength, 0)) { - throw new RASAeroExportException("Launch lug length can not be 0"); + throw new RASAeroExportException("Launch lug length can not be 0."); } this.launchLugLength = launchLugLength; } @@ -101,7 +101,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException { if (MathUtil.equals(railGuideDiameter, 0)) { - throw new RASAeroExportException("Rail button diameter can not be 0"); + throw new RASAeroExportException("Rail button diameter can not be 0."); } this.railGuideDiameter = railGuideDiameter; } @@ -112,7 +112,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException { if (MathUtil.equals(railGuideHeight, 0)) { - throw new RASAeroExportException("Rail button height can not be 0"); + throw new RASAeroExportException("Rail button height can not be 0."); } this.railGuideHeight = railGuideHeight; } @@ -123,7 +123,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException { if (MathUtil.equals(launchShoeArea, 0)) { - throw new RASAeroExportException("Launch shoe area can not be 0"); + throw new RASAeroExportException("Launch shoe area can not be 0."); } this.launchShoeArea = launchShoeArea; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index 00b5eb954..8d6d4a960 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -18,11 +18,11 @@ public interface BodyTubeDTOAdapter { setFin(new FinDTO((TrapezoidFinSet) child)); } else if (child instanceof LaunchLug) { if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format("Already added a rail button, ignoring launch lug '%s'", child.getName())); + warnings.add(String.format("Already added a rail button, ignoring launch lug '%s'.", child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format("Already added a launch shoe, ignoring launch lug '%s'", child.getName())); + warnings.add(String.format("Already added a launch shoe, ignoring launch lug '%s'.", child.getName())); continue; } @@ -38,11 +38,11 @@ public interface BodyTubeDTOAdapter { } } else if (child instanceof RailButton) { if (!MathUtil.equals(getLaunchLugDiameter(), 0) || !MathUtil.equals(getLaunchLugLength(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format("Already added a launch lug, ignoring rail button '%s'", child.getName())); + warnings.add(String.format("Already added a launch lug, ignoring rail button '%s'.", child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format("Already added a launch shoe, ignoring rail button '%s'", child.getName())); + warnings.add(String.format("Already added a launch shoe, ignoring rail button '%s'.", child.getName())); continue; } @@ -51,7 +51,7 @@ public interface BodyTubeDTOAdapter { setRailGuideHeight(button.getTotalHeight() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); if (button.getInstanceCount() != 2) { - warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2", + warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2.", button.getName(), button.getInstanceCount())); } } else { diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 541094e3d..ba216c561 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -14,7 +14,6 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; -import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @@ -24,11 +23,8 @@ import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; -import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.MathUtil; -import java.util.List; - @XmlRootElement(name = RASAeroCommonConstants.BOOSTER) @XmlAccessorType(XmlAccessType.FIELD) public class BoosterDTO implements BodyTubeDTOAdapter { @@ -105,43 +101,43 @@ public class BoosterDTO implements BodyTubeDTOAdapter { int stageNr = rocket.getChildPosition(stage); // Use this instead of stage.getStageNumber() in case there are parallel stages in the design if (stageNr != 1 && stageNr != 2) { - throw new RASAeroExportException(String.format("Invalid stage number '%d' for booster stage '%s'", stageNr, stage.getName())); + throw new RASAeroExportException(String.format("Invalid stage number '%d' for booster stage '%s'.", stageNr, stage.getName())); } if (stage.getChildCount() == 0) { - throw new RASAeroExportException(String.format("Stage '%s' can not be empty", stage.getName())); + throw new RASAeroExportException(String.format("Stage '%s' may not be empty.", stage.getName())); } RocketComponent firstChild = stage.getChild(0); if (!(firstChild instanceof BodyTube) && !(firstChild instanceof Transition && !(firstChild instanceof NoseCone))) { - throw new RASAeroExportException(String.format("First component of stage '%s' must be a body tube or transition", stage.getName())); + throw new RASAeroExportException(String.format("First component of stage '%s' must be a body tube or transition.", stage.getName())); } final BodyTube firstTube; if (firstChild instanceof Transition) { if (stage.getChildCount() == 1 || !(stage.getChild(1) instanceof BodyTube)) { throw new RASAeroExportException( - String.format("When the first component of stage '%s' is a transition, the second one must be a body tube", + String.format("When the first component of stage '%s' is a transition, the second one must be a body tube.", stage.getName())); } Transition transition = (Transition) firstChild; SymmetricComponent previousComponent = transition.getPreviousSymmetricComponent(); if (previousComponent == null) { - throw new RASAeroExportException(String.format("No previous component for '%s' in stage '%s'", + throw new RASAeroExportException(String.format("No previous component for '%s' in stage '%s'.", firstChild.getName(), stage.getName())); } if (!MathUtil.equals(transition.getForeRadius(), previousComponent.getAftRadius())) { throw new RASAeroExportException( - String.format("Transition '%s' in stage '%s' must have the same fore radius as the aft radius of its previous component '%s'", + String.format("Transition '%s' in stage '%s' must have the same fore radius as the aft radius of its previous component '%s'.", transition.getName(), stage.getName(), previousComponent.getName())); } firstTube = (BodyTube) stage.getChild(1); if (!MathUtil.equals(firstTube.getOuterRadius(), transition.getAftRadius())) { throw new RASAeroExportException( - String.format("Radius of '%s' in stage '%s' must be the same as the aft radius of '%s", + String.format("Radius of '%s' in stage '%s' must be the same as the aft radius of '%s'.", firstTube.getName(), stage.getName(), transition.getName())); } @@ -150,13 +146,13 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setInsideDiameter(transition.getForeRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); if (stage.getChildCount() > 2) { - warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components", + warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components.", stage.getName(), stage.getChildCount() - 2)); } } else { firstTube = (BodyTube) stage.getChild(0); if (stage.getChildCount() > 1) { - warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components", + warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components.", stage.getName(), stage.getChildCount() - 1)); } } @@ -166,7 +162,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); if (finSet == null) { throw new RASAeroExportException( - String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet", + String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet.", firstTube.getName(), stage.getName())); } setFin(new FinDTO(finSet)); @@ -201,7 +197,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setLength(Double length) { if (MathUtil.equals(length, 0)) { - errors.add(String.format("Length of '%s' must be greater than 0", component.getName())); + errors.add(String.format("Length of '%s' must be greater than 0.", component.getName())); return; } this.length = length; @@ -213,7 +209,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setDiameter(Double diameter) throws RASAeroExportException { if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0", component.getName())); + throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0.", component.getName())); } this.diameter = diameter; } @@ -224,7 +220,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setInsideDiameter(Double insideDiameter) throws RASAeroExportException { if (MathUtil.equals(insideDiameter, 0)) { - throw new RASAeroExportException(String.format("Inside diameter of '%s' must be greater than 0", component.getName())); + throw new RASAeroExportException(String.format("Inside diameter of '%s' must be greater than 0.", component.getName())); } this.insideDiameter = insideDiameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java index 947f4a1e2..8d65764a4 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java @@ -57,7 +57,7 @@ public class FinDTO { int finCount = fin.getFinCount(); if (finCount < 3 || finCount > 8) { throw new RASAeroExportException( - String.format("Fin set '%s' must have a fin count between 3 and 8", fin.getName())); + String.format("Fin set '%s' must have a fin count between 3 and 8.", fin.getName())); } setCount(fin.getFinCount()); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index 887b5d3f4..8097e96d7 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -82,17 +82,17 @@ public class RocketDesignDTO { try { RocketComponent component = sustainer.getChild(i); if (i == 0 && !(component instanceof NoseCone)) { - errors.add("First component of the sustainer must be a nose cone"); + errors.add("First component of the sustainer must be a nose cone."); return; } else if (i == 1 && !(component instanceof BodyTube)) { - errors.add("Second component of the sustainer must be a body tube"); + errors.add("Second component of the sustainer must be a body tube."); return; } if (component instanceof BodyTube) { addExternalPart(new BodyTubeDTO((BodyTube) component, warnings, errors)); } else if (component instanceof NoseCone) { if (i != 0) { - errors.add("A nose cone can only be the first component of the rocket"); + errors.add("A nose cone can only be the first component of the rocket."); return; } addExternalPart(new NoseConeDTO((NoseCone) component, warnings, errors)); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java index b0756df94..a4a43e2fa 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -36,16 +36,16 @@ public class TransitionDTO extends BasePartDTO { super(transition, warnings, errors); if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { - throw new RASAeroExportException("RASAero only supports conical transitions"); + throw new RASAeroExportException("RASAero only supports conical transitions."); } SymmetricComponent previousComp = transition.getPreviousSymmetricComponent(); if (previousComp == null) { - throw new RASAeroExportException(String.format("Transition '%s' has no previous component", transition.getName())); + throw new RASAeroExportException(String.format("Transition '%s' has no previous component.", transition.getName())); } if (!MathUtil.equals(transition.getForeRadius(), previousComp.getAftRadius())) { throw new RASAeroExportException( - String.format("Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not (%f)", + String.format("Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f.", transition.getName(), previousComp.getAftRadius(), transition.getForeRadius())); } From 9e8163e3e11b89d3d27edd56c4b31a379680da27 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 19:08:40 +0200 Subject: [PATCH 11/76] Import motor settings even if the stage is disabled --- .../importt/SimulationListHandler.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index a33efc456..234349bf3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -127,13 +127,9 @@ public class SimulationListHandler extends AbstractElementHandler { } // Add motors to the rocket - addMotorToStage(0, sustainerEngine, sustainerIgnitionDelay, id, warnings); - if (includeBooster1) { - addMotorToStage(1, booster1Engine, booster1IgnitionDelay, id, warnings); - } - if (includeBooster2) { - addMotorToStage(2, booster2Engine, 0.0, id, warnings); - } + addMotorToStage(0, sustainerEngine, sustainerIgnitionDelay, id, true, warnings); + addMotorToStage(1, booster1Engine, booster1IgnitionDelay, id, includeBooster1, warnings); + addMotorToStage(2, booster2Engine, 0.0, id, includeBooster2, warnings); // Set separation settings setSeparationDelay(0, 0.0, id); @@ -152,8 +148,18 @@ public class SimulationListHandler extends AbstractElementHandler { context.getOpenRocketDocument().addSimulation(sim); } + /** + * Add a new motor to a stage + * @param stageNr number of the stage to add the motor to + * @param motor motor to add + * @param ignitionDelay ignition delay of the motor + * @param id flight config id to alter + * @param enableMotorMount whether the motor mount should be enabled or disabled + * @param warnings + */ private void addMotorToStage(final int stageNr, final Motor motor, final Double ignitionDelay, - final FlightConfigurationId id, final WarningSet warnings) { + final FlightConfigurationId id, boolean enableMotorMount, + final WarningSet warnings) { if (motor == null || rocket.getStage(stageNr) == null) { return; } @@ -179,6 +185,7 @@ public class SimulationListHandler extends AbstractElementHandler { motorConfig.setIgnitionEvent(IgnitionEvent.AUTOMATIC); } mount.setMotorConfig(motorConfig, id); + mount.setMotorMount(enableMotorMount); } private void setSeparationDelay(final int stageNr, Double separationDelay, @@ -205,9 +212,6 @@ public class SimulationListHandler extends AbstractElementHandler { mount = (MotorMount) component; } } - if (mount != null) { - mount.setMotorMount(true); - } return mount; } } From 020ea2cf3f4299c7a0795db4b66bcafab26636d8 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 19:17:44 +0200 Subject: [PATCH 12/76] Fix RD2 position for small body tubes and rule 1 --- .../sf/openrocket/file/rasaero/importt/RecoveryHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java index 6af7ccd45..faeaa5892 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RecoveryHandler.java @@ -367,6 +367,10 @@ public class RecoveryHandler extends AbstractElementHandler { offset += parentBodyTube.getOuterRadius() * 2.25; // 1.125 calibers recoveryDevice.setAxialMethod(AxialMethod.TOP); if (offset + recoveryDevice.getLength() > parentBodyTube.getLength()) { + // For rule 1, device 2 should be below device 1, so just in case, put this at the bottom instead of at the top + if (bodyTubes.size() == 1) { + recoveryDevice.setAxialMethod(AxialMethod.BOTTOM); + } offset = 0; } recoveryDevice.setAxialOffset(offset); From db46daf4f272e914f4c0d621ac9aff9ea8df0419 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 19:37:04 +0200 Subject: [PATCH 13/76] Fix wrong hasMotors method The issue is that FlightConfigurableParameterSet.size() returns the map size minus 1, so it already ignores the default config --- core/src/net/sf/openrocket/rocketcomponent/BodyTube.java | 4 +--- core/src/net/sf/openrocket/rocketcomponent/InnerTube.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 40b292044..0064356a9 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -451,9 +451,7 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou @Override public boolean hasMotor() { - // the default MotorInstance is the EMPTY_INSTANCE. - // If the class contains more instances, at least one will have motors. - return ( 1 < this.motors.size()); + return this.motors.size() > 0; } @Override diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 581d8583c..1e17bb335 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -363,7 +363,7 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab @Override public boolean hasMotor() { // the default MotorInstance is the EMPTY_INSTANCE. If we have more than that, then the other instance will have a motor. - return ( 1 < this.motors.size()); + return this.motors.size() > 0; } @Override From 7bef89c7daa0dfd96c710e44fa44f530a000dd9b Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 20:35:01 +0200 Subject: [PATCH 14/76] Implement RASAero simulation exporting --- .../file/rasaero/RASAeroCommonConstants.java | 68 +++- .../file/rasaero/export/LaunchSiteDTO.java | 8 + .../file/rasaero/export/NoseConeDTO.java | 3 +- .../rasaero/export/RASAeroDocumentDTO.java | 4 - .../file/rasaero/export/RASAeroSaver.java | 16 +- .../file/rasaero/export/RecoveryDTO.java | 8 + .../file/rasaero/export/SimulationDTO.java | 384 ++++++++++++++++++ .../rasaero/export/SimulationListDTO.java | 69 ++++ .../rasaero/importt/RASAeroMotorsLoader.java | 25 +- .../net/sf/openrocket/logging/MessageSet.java | 2 +- 10 files changed, 569 insertions(+), 18 deletions(-) create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index da5481ca8..c33965e6d 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero; import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.FinSet; @@ -154,15 +155,29 @@ public class RASAeroCommonConstants { public static final String SIMULATION_LIST = "SimulationList"; public static final String SIMULATION = "Simulation"; public static final String SUSTAINER_ENGINE = "SustainerEngine"; - // TODO: SustainerLaunchWt, SustainerCG? + public static final String SUSTAINER_LAUNCH_WT = "SustainerLaunchWt"; + public static final String SUSTAINER_NOZZLE_DIAMETER = "SustainerNozzleDiameter"; + public static final String SUSTAINER_CG = "SustainerCG"; public static final String SUSTAINER_IGNITION_DELAY = "SustainerIgnitionDelay"; public static final String BOOSTER1_ENGINE = "Booster1Engine"; public static final String BOOSTER1_SEPARATION_DELAY = "Booster1SeparationDelay"; // Delay after booster burnout to separate public static final String BOOSTER1_IGNITION_DELAY = "Booster1IgnitionDelay"; + public static final String BOOSTER1_LAUNCH_WT = "Booster1LaunchWt"; + public static final String BOOSTER1_NOZZLE_DIAMETER = "Booster1NozzleDiameter"; + public static final String BOOSTER1_CG = "Booster1CG"; public static final String INCLUDE_BOOSTER1 = "IncludeBooster1"; public static final String BOOSTER2_ENGINE = "Booster2Engine"; public static final String BOOSTER2_SEPARATION_DELAY = "Booster2Delay"; // Delay after booster burnout to separate + public static final String BOOSTER2_LAUNCH_WT = "Booster2LaunchWt"; + public static final String BOOSTER2_NOZZLE_DIAMETER = "Booster2NozzleDiameter"; + public static final String BOOSTER2_CG = "Booster2CG"; public static final String INCLUDE_BOOSTER2 = "IncludeBooster2"; + public static final String FLIGHT_TIME = "FlightTime"; + public static final String TIME_TO_APOGEE = "TimetoApogee"; + public static final String MAX_ALTITUDE = "MaxAltitude"; + public static final String MAX_VELOCITY = "MaxVelocity"; + public static final String OPTIMUM_WT = "OptimumWt"; + public static final String OPTIMUM_MAX_ALT = "OptimumMaxAlt"; /** @@ -353,6 +368,57 @@ public class RASAeroCommonConstants { } } + public static String OPENROCKET_TO_RASAERO_MANUFACTURER(Manufacturer manufacturer) { + if (manufacturer.matches("AeroTech")) { + return "AT"; + } else if (manufacturer.matches("Estes")) { + return "ES"; + } else if (manufacturer.matches("Apogee")) { + return "AP"; + } else if (manufacturer.matches("Quest")) { + return "QU"; + } else if (manufacturer.matches("Cesaroni")) { + return "CTI"; + } else if (manufacturer.matches("NoThrust")) { + return "NoThrust"; + } else if (manufacturer.matches("Ellis Mountain")) { + return "EM"; + } else if (manufacturer.matches("Contrail")) { + return "Contrail"; + } else if (manufacturer.matches("Rocketvision")) { + return "RV"; + } else if (manufacturer.matches("Roadrunner Rocketry")) { + return "RR"; + } else if (manufacturer.matches("Sky Ripper Systems")) { + return "SRS"; + } else if (manufacturer.matches("Loki Research")) { + return "LR"; + } else if (manufacturer.matches("Public Missiles, Ltd.")) { + return "PML"; + } else if (manufacturer.matches("Kosdon by AeroTech")) { + return "KBA"; + } else if (manufacturer.matches("Gorilla Rocket Motors")) { + return "GM"; + } else if (manufacturer.matches("RATT Works")) { + return "RTW"; + } else if (manufacturer.matches("HyperTEK")) { + return "HT"; + } else if (manufacturer.matches("Animal Motor Works")) { + return "AMW"; + } else if (manufacturer.matches("Loki")) { + return "CT"; + } else if (manufacturer.matches("AMW ProX")) { + return "AMW/ProX"; + } else if (manufacturer.matches("Loki Research EX")) { + return "LR-EX"; + } else if (manufacturer.matches("Derek Deville DEAP EX")) { + return "DEAP-EX"; + } else if (manufacturer.matches("Historical")) { + return "Hist"; + } + return manufacturer.getSimpleName(); + } + public static DeploymentConfiguration.DeployEvent RASAERO_TO_OPENROCKET_DEPLOY_EVENT(String deployEvent, WarningSet warnings) { if (DEPLOYMENT_NONE.equals(deployEvent)) { return DeploymentConfiguration.DeployEvent.NEVER; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java index 7c8fc83fe..de6b303a0 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java @@ -1,4 +1,12 @@ package net.sf.openrocket.file.rasaero.export; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = RASAeroCommonConstants.LAUNCH_SITE) +@XmlAccessorType(XmlAccessType.FIELD) public class LaunchSiteDTO { } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java index cd1e94acd..95c3d5498 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java @@ -22,7 +22,8 @@ public class NoseConeDTO extends BasePartDTO { @XmlElement(name = RASAeroCommonConstants.SHAPE) private String shape; @XmlElement(name = RASAeroCommonConstants.BLUNT_RADIUS) - private double bluntRadius = 0; + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double bluntRadius = 0d; @XmlElement(name = RASAeroCommonConstants.POWER_LAW) @XmlJavaTypeAdapter(CustomDoubleAdapter.class) private Double powerLaw; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java index fc5ac0051..ef4420a83 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java @@ -29,10 +29,6 @@ public class RASAeroDocumentDTO { @XmlElement(name = RASAeroCommonConstants.MACH_ALT) private String machAlt = ""; // Currently not implemented - /* - @XmlElementWrapper(name=RASAeroCommonConstants.SIMULATION_LIST) - @XmlElement(name=RASAeroCommonConstants.SIMULATION) - */ @XmlElement(name = RASAeroCommonConstants.SIMULATION_LIST) private SimulationListDTO simulationList = null; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java index 110cac778..d418e7ca3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -50,9 +50,10 @@ public class RASAeroSaver extends RocketSaver { errors.add(e.getMessage()); } catch (Exception e) { log.error("Could not marshall a design to RASAero format. " + e.getMessage()); + throw new RuntimeException("Could not marshall a design to RASAero format. " + e.getMessage()); } - return null; + throw new RuntimeException("Could not marshall a design to RASAero format."); } @Override @@ -80,7 +81,7 @@ public class RASAeroSaver extends RocketSaver { private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); rad.setDesign(toRocketDesignDTO(doc.getRocket(), warnings, errors)); - + rad.setSimulationList(toSimulationListDTO(doc, warnings, errors)); return rad; } @@ -92,4 +93,15 @@ public class RASAeroSaver extends RocketSaver { private RocketDesignDTO toRocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { return new RocketDesignDTO(rocket, warnings, errors); } + + /** + * Create a list of simulations. + * @param document document that contains simulations + * @param warnings list to add export warnings to + * @param errors list to add export errors to + * @return the RASAero simulation list + */ + private SimulationListDTO toSimulationListDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { + return new SimulationListDTO(document, warnings, errors); + } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java index 8cbb8233f..ba79f415b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java @@ -1,4 +1,12 @@ package net.sf.openrocket.file.rasaero.export; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = RASAeroCommonConstants.RECOVERY) +@XmlAccessorType(XmlAccessType.FIELD) public class RecoveryDTO { } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java new file mode 100644 index 000000000..95533d7f1 --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -0,0 +1,384 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorConfiguration; +import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.List; +import java.util.Map; + +@XmlRootElement(name = RASAeroCommonConstants.SIMULATION) +@XmlAccessorType(XmlAccessType.FIELD) +public class SimulationDTO { + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_ENGINE) + private String sustainerEngine; + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_LAUNCH_WT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sustainerLaunchWt = 0d; + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_NOZZLE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sustainerNozzleDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_CG) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sustainerCG = 0d; + @XmlElement(name = RASAeroCommonConstants.SUSTAINER_IGNITION_DELAY) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double sustainerIgnitionDelay = 0d; + + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_ENGINE) + private String booster1Engine; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_LAUNCH_WT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1LaunchWt = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_SEPARATION_DELAY) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1SeparationDelay = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_IGNITION_DELAY) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1IgnitionDelay = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_CG) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1CG = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER1_NOZZLE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster1NozzleDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.INCLUDE_BOOSTER1) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean includeBooster1 = false; + + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_ENGINE) + private String booster2Engine; + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_LAUNCH_WT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster2LaunchWt = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_SEPARATION_DELAY) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster2Delay = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_CG) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster2CG = 0d; + @XmlElement(name = RASAeroCommonConstants.BOOSTER2_NOZZLE_DIAMETER) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double booster2NozzleDiameter = 0d; + @XmlElement(name = RASAeroCommonConstants.INCLUDE_BOOSTER2) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean includeBooster2 = false; + + @XmlElement(name = RASAeroCommonConstants.FLIGHT_TIME) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double flightTime = 0d; + @XmlElement(name = RASAeroCommonConstants.TIME_TO_APOGEE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double timetoApogee = 0d; + @XmlElement(name = RASAeroCommonConstants.MAX_ALTITUDE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double maxAltitude = 0d; + @XmlElement(name = RASAeroCommonConstants.MAX_VELOCITY) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double maxVelocity = 0d; + @XmlElement(name = RASAeroCommonConstants.OPTIMUM_WT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double optimumWt = 0d; + @XmlElement(name = RASAeroCommonConstants.OPTIMUM_MAX_ALT) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double optimumMaxAlt = 0d; + + /** + * We need a default, no-args constructor. + */ + public SimulationDTO() { + } + + /** + * RASAero Simulation object. + * @param rocket the rocket + * @param simulation the simulation to convert + * @param mounts a map of stages and their corresponding motor mount (only 1 mount per stage allowed) + * @param motors a list of RASAero motors + * @param warnings a list to add export warnings to + * @param errors a list to add export errors to + */ + public SimulationDTO(Rocket rocket, Simulation simulation, Map mounts, List motors, + WarningSet warnings, ErrorSet errors) { + FlightConfigurationId fcid = simulation.getFlightConfigurationId(); + if (fcid == null) { + warnings.add(String.format("Empty simulation '%s', ignoring.", simulation.getName())); + return; + } + + for (Map.Entry mountSet : mounts.entrySet()) { + AxialStage stage = mountSet.getKey(); + MotorMount mount = mountSet.getValue(); + if (mount == null || stage == null) { + continue; + } + + MotorConfiguration motorConfig = mount.getMotorConfig(fcid); + StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(fcid); + int stageNr = rocket.getChildPosition(stage); + + switch (stageNr) { + // Sustainer + case 0: + setSustainerEngine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setSustainerLaunchWt(stage.getSectionMass()); + setSustainerCG(stage.getCG().x); // TODO: use aggregate CG... + setSustainerIgnitionDelay(motorConfig.getIgnitionDelay()); + break; + // Booster 1 + case 1: + setBooster1Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setBooster1LaunchWt(stage.getSectionMass()); + setBooster1CG(stage.getCG().x); // TODO: use aggregate CG... + setBooster1IgnitionDelay(motorConfig.getIgnitionDelay()); + setBooster1SeparationDelay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) + setIncludeBooster1(mount.isMotorMount()); + break; + // Booster 2 + case 2: + setBooster2Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setBooster2LaunchWt(stage.getSectionMass()); + setBooster2CG(stage.getCG().x); // TODO: use aggregate CG... + setBooster2Delay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) + setIncludeBooster2(mount.isMotorMount()); + break; + // Invalid + default: + errors.add(String.format("Invalid stage number '%d' for simulation '%s'", + stageNr, simulation.getName())); + } + } + } + + /** + * Format an OpenRocket motor as a RASAero motor. + * @param motors list of available RASAero motors + * @param ORMotor OpenRocket motor + * @return a RASAero String representation of a motor + */ + private String getRASAeroMotor(List motors, Motor ORMotor, WarningSet warnings) { + if (!(ORMotor instanceof ThrustCurveMotor)) { + return null; + } + + for (ThrustCurveMotor motor : motors) { + if (ORMotor.getDesignation().equals(motor.getDesignation()) && + ((ThrustCurveMotor) ORMotor).getManufacturer().matches(motor.getManufacturer().getDisplayName())) { + return motor.getDesignation() + + " (" + RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; + } + } + + warnings.add(String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation())); + return null; + } + + + public String getSustainerEngine() { + return sustainerEngine; + } + + public void setSustainerEngine(String sustainerEngine) { + this.sustainerEngine = sustainerEngine; + } + + public Double getSustainerLaunchWt() { + return sustainerLaunchWt; + } + + public void setSustainerLaunchWt(Double sustainerLaunchWt) { + this.sustainerLaunchWt = sustainerLaunchWt; + } + + public Double getSustainerNozzleDiameter() { + return sustainerNozzleDiameter; + } + + public void setSustainerNozzleDiameter(Double sustainerNozzleDiameter) { + this.sustainerNozzleDiameter = sustainerNozzleDiameter; + } + + public Double getSustainerCG() { + return sustainerCG; + } + + public void setSustainerCG(Double sustainerCG) { + this.sustainerCG = sustainerCG; + } + + public Double getSustainerIgnitionDelay() { + return sustainerIgnitionDelay; + } + + public void setSustainerIgnitionDelay(Double sustainerIgnitionDelay) { + this.sustainerIgnitionDelay = sustainerIgnitionDelay; + } + + public String getBooster1Engine() { + return booster1Engine; + } + + public void setBooster1Engine(String booster1Engine) { + this.booster1Engine = booster1Engine; + } + + public Double getBooster1LaunchWt() { + return booster1LaunchWt; + } + + public void setBooster1LaunchWt(Double booster1LaunchWt) { + this.booster1LaunchWt = booster1LaunchWt; + } + + public Double getBooster1SeparationDelay() { + return booster1SeparationDelay; + } + + public void setBooster1SeparationDelay(Double booster1SeparationDelay) { + this.booster1SeparationDelay = booster1SeparationDelay; + } + + public Double getBooster1IgnitionDelay() { + return booster1IgnitionDelay; + } + + public void setBooster1IgnitionDelay(Double booster1IgnitionDelay) { + this.booster1IgnitionDelay = booster1IgnitionDelay; + } + + public Double getBooster1CG() { + return booster1CG; + } + + public void setBooster1CG(Double booster1CG) { + this.booster1CG = booster1CG; + } + + public Double getBooster1NozzleDiameter() { + return booster1NozzleDiameter; + } + + public void setBooster1NozzleDiameter(Double booster1NozzleDiameter) { + this.booster1NozzleDiameter = booster1NozzleDiameter; + } + + public Boolean getIncludeBooster1() { + return includeBooster1; + } + + public void setIncludeBooster1(Boolean includeBooster1) { + this.includeBooster1 = includeBooster1; + } + + public String getBooster2Engine() { + return booster2Engine; + } + + public void setBooster2Engine(String booster2Engine) { + this.booster2Engine = booster2Engine; + } + + public Double getBooster2LaunchWt() { + return booster2LaunchWt; + } + + public void setBooster2LaunchWt(Double booster2LaunchWt) { + this.booster2LaunchWt = booster2LaunchWt; + } + + public Double getBooster2Delay() { + return booster2Delay; + } + + public void setBooster2Delay(Double booster2Delay) { + this.booster2Delay = booster2Delay; + } + + public Double getBooster2CG() { + return booster2CG; + } + + public void setBooster2CG(Double booster2CG) { + this.booster2CG = booster2CG; + } + + public Double getBooster2NozzleDiameter() { + return booster2NozzleDiameter; + } + + public void setBooster2NozzleDiameter(Double booster2NozzleDiameter) { + this.booster2NozzleDiameter = booster2NozzleDiameter; + } + + public Boolean getIncludeBooster2() { + return includeBooster2; + } + + public void setIncludeBooster2(Boolean includeBooster2) { + this.includeBooster2 = includeBooster2; + } + + public Double getFlightTime() { + return flightTime; + } + + public void setFlightTime(Double flightTime) { + this.flightTime = flightTime; + } + + public Double getTimetoApogee() { + return timetoApogee; + } + + public void setTimetoApogee(Double timetoApogee) { + this.timetoApogee = timetoApogee; + } + + public Double getMaxAltitude() { + return maxAltitude; + } + + public void setMaxAltitude(Double maxAltitude) { + this.maxAltitude = maxAltitude; + } + + public Double getMaxVelocity() { + return maxVelocity; + } + + public void setMaxVelocity(Double maxVelocity) { + this.maxVelocity = maxVelocity; + } + + public Double getOptimumWt() { + return optimumWt; + } + + public void setOptimumWt(Double optimumWt) { + this.optimumWt = optimumWt; + } + + public Double getOptimumMaxAlt() { + return optimumMaxAlt; + } + + public void setOptimumMaxAlt(Double optimumMaxAlt) { + this.optimumMaxAlt = optimumMaxAlt; + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java index a3af9c971..17dda78d1 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -1,4 +1,73 @@ package net.sf.openrocket.file.rasaero.export; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.file.rasaero.importt.RASAeroMotorsLoader; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@XmlRootElement(name = RASAeroCommonConstants.SIMULATION_LIST) +@XmlAccessorType(XmlAccessType.FIELD) public class SimulationListDTO { + @XmlElement(name = RASAeroCommonConstants.SIMULATION) + private final List simulations = new LinkedList<>(); + + /** + * We need a default, no-args constructor. + */ + public SimulationListDTO() { + } + + public SimulationListDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { + Map mounts = new HashMap<>(); + Rocket rocket = document.getRocket(); + + // Fetch all the motor mounts from the design + for (RocketComponent child : rocket.getChildren()) { + AxialStage stage = (AxialStage) child; + if (mounts.containsKey(stage)) { + continue; + } + for (RocketComponent stageChild : stage.getChildren()) { + // We can only really use body tubes as motor mounts, since other stuff (e.g. inner tube) is not supported in RASAero + if (stageChild instanceof BodyTube && ((BodyTube) stageChild).hasMotor()) { + mounts.put(stage, (BodyTube) stageChild); + } + } + } + + // Load all RASAero motors + List motors = RASAeroMotorsLoader.loadAllRASAeroMotors(warnings); + + for (Simulation simulation : document.getSimulations()) { + addSimulation(new SimulationDTO(rocket, simulation, mounts, motors, warnings, errors)); + } + + motors.clear(); + } + + public List getSimulations() { + return simulations; + } + + public void addSimulation(SimulationDTO simulation) { + simulations.add(simulation); + } + } diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java index b24df532f..53676d405 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java @@ -1,10 +1,13 @@ package net.sf.openrocket.file.rasaero.importt; +import net.sf.openrocket.file.motor.GeneralMotorLoader; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.database.motor.ThrustCurveMotorSet; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.startup.Application; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -22,7 +25,7 @@ public abstract class RASAeroMotorsLoader { return null; } if (allMotors == null) { - loadAllMotors(); + loadAllMotors(warnings); } /* RASAero file motor strings are formatted as " ()" @@ -52,17 +55,18 @@ public abstract class RASAeroMotorsLoader { } } - // Not currently used, because it causes some compatibility issues when e.g. wanting to open the RASAero motor + // Not currently used for importing, because it causes some compatibility issues when e.g. wanting to open the RASAero motor // in the motor selection table (because it is not present there). // It's probably also better to load OR-native motors. // But I'll leave this in, in case it's needed in the future. - /* + /** * Loads all original RASAero motors. * @param warnings The warning set to add import warnings to. + * @return the loaded motors * @throws RuntimeException If the RASAero motors file could not be found. - * - private static void loadAllRASAeroMotors(WarningSet warnings) throws RuntimeException { - allMotors = new ArrayList<>(); + */ + public static List loadAllRASAeroMotors(WarningSet warnings) throws RuntimeException { + List RASAeroMotors = new ArrayList<>(); GeneralMotorLoader loader = new GeneralMotorLoader(); ClassLoader classloader = Thread.currentThread().getContextClassLoader(); @@ -74,22 +78,25 @@ public abstract class RASAeroMotorsLoader { try { List motors = loader.load(is, fileName); for (ThrustCurveMotor.Builder builder : motors) { - allMotors.add(builder.build()); + RASAeroMotors.add(builder.build()); } } catch (IOException e) { warnings.add("Error during motor loading: " + e.getMessage()); } - }*/ + + return RASAeroMotors; + } /** * Loads the OpenRocket motors database. */ - private static void loadAllMotors() { + private static void loadAllMotors(WarningSet warnings) { allMotors = new ArrayList<>(); List database = Application.getThrustCurveMotorSetDatabase().getMotorSets(); for (ThrustCurveMotorSet set : database) { allMotors.addAll(set.getMotors()); } + //allMotors.addAll(loadAllRASAeroMotors(warnings)); } } diff --git a/core/src/net/sf/openrocket/logging/MessageSet.java b/core/src/net/sf/openrocket/logging/MessageSet.java index 98642d1aa..064c52cf5 100644 --- a/core/src/net/sf/openrocket/logging/MessageSet.java +++ b/core/src/net/sf/openrocket/logging/MessageSet.java @@ -74,7 +74,7 @@ public abstract class MessageSet extends AbstractSet imple @Override public Iterator iterator() { final Iterator iterator = messages.iterator(); - return new Iterator<>() { + return new Iterator() { @Override public boolean hasNext() { return iterator.hasNext(); From c60fc8753825f2a0f62a21b5046edfb2d529c1ef Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 21:14:09 +0200 Subject: [PATCH 15/76] Implement RASAero launch site exporting --- .../file/rasaero/export/LaunchSiteDTO.java | 109 ++++++++++++++++++ .../file/rasaero/export/RASAeroSaver.java | 13 +++ 2 files changed, 122 insertions(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java index de6b303a0..88794e152 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java @@ -1,12 +1,121 @@ package net.sf.openrocket.file.rasaero.export; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.simulation.SimulationOptions; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement(name = RASAeroCommonConstants.LAUNCH_SITE) @XmlAccessorType(XmlAccessType.FIELD) public class LaunchSiteDTO { + + @XmlElement(name = RASAeroCommonConstants.LAUNCH_ALTITUDE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double altitude = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_PRESSURE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double pressure = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_ROD_ANGLE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double rodAngle = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_ROD_LENGTH) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double rodLength = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_TEMPERATURE) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double temperature = 0d; + @XmlElement(name = RASAeroCommonConstants.LAUNCH_WIND_SPEED) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double windSpeed = 0d; + + /** + * We need a default, no-args constructor. + */ + public LaunchSiteDTO() { + } + + public LaunchSiteDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { + for (Simulation sim : document.getSimulations()) { + SimulationOptions options = sim.getSimulatedConditions(); + if (options == null) { + continue; + } + + setAltitude(options.getLaunchAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); + setPressure(options.getLaunchPressure() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_PRESSURE); + setTemperature(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TEMPERATURE(options.getLaunchTemperature())); + setRodAngle(options.getLaunchRodAngle() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ANGLE); + setRodLength(options.getLaunchRodLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setWindSpeed(options.getWindSpeedAverage() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SPEED); + return; + } + + // If we can't get settings from the sims, use the launch site settings from the preferences + Preferences prefs = Application.getPreferences(); + setAltitude(prefs.getLaunchAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); + setPressure(prefs.getLaunchPressure() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_PRESSURE); + setTemperature(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TEMPERATURE(prefs.getLaunchTemperature())); + setRodAngle(prefs.getLaunchRodAngle() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ANGLE); + setRodLength(prefs.getLaunchRodLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setWindSpeed(prefs.getWindSpeedAverage() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SPEED); + } + + public Double getAltitude() { + return altitude; + } + + public void setAltitude(Double altitude) { + this.altitude = altitude; + } + + public Double getPressure() { + return pressure; + } + + public void setPressure(Double pressure) { + this.pressure = pressure; + } + + public Double getRodAngle() { + return rodAngle; + } + + public void setRodAngle(Double rodAngle) { + this.rodAngle = rodAngle; + } + + public Double getRodLength() { + return rodLength; + } + + public void setRodLength(Double rodLength) { + this.rodLength = rodLength; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(Double windSpeed) { + this.windSpeed = windSpeed; + } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java index d418e7ca3..a2b372c93 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -81,7 +81,9 @@ public class RASAeroSaver extends RocketSaver { private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); rad.setDesign(toRocketDesignDTO(doc.getRocket(), warnings, errors)); + rad.setLaunchSite(toLaunchSiteDTO(doc, warnings, errors)); rad.setSimulationList(toSimulationListDTO(doc, warnings, errors)); + return rad; } @@ -94,6 +96,17 @@ public class RASAeroSaver extends RocketSaver { return new RocketDesignDTO(rocket, warnings, errors); } + /** + * Create RASAero launch site settings. + * @param document document that contains simulations to take the launch site settings from + * @param warnings list to add export warnings to + * @param errors list to add export errors to + * @return the RASAero launch site settings + */ + private LaunchSiteDTO toLaunchSiteDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { + return new LaunchSiteDTO(document, warnings, errors); + } + /** * Create a list of simulations. * @param document document that contains simulations From f8e74b60f03f0822e3ec230286eb29989227f019 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 21:14:31 +0200 Subject: [PATCH 16/76] Replace explicit launch site defaults with methods --- .../openrocket/simulation/SimulationOptions.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/net/sf/openrocket/simulation/SimulationOptions.java b/core/src/net/sf/openrocket/simulation/SimulationOptions.java index 0025c1f5b..9b9a8e465 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationOptions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationOptions.java @@ -66,16 +66,16 @@ public class SimulationOptions implements ChangeSource, Cloneable { * and converts them into a WorldCoordinate when converting to SimulationConditions. */ - private double launchAltitude = preferences.getDouble(Preferences.LAUNCH_ALTITUDE, 0); - private double launchLatitude = preferences.getDouble(Preferences.LAUNCH_LATITUDE, 28.61); - private double launchLongitude = preferences.getDouble(Preferences.LAUNCH_LONGITUDE, -80.60); + private double launchAltitude = preferences.getLaunchAltitude(); + private double launchLatitude = preferences.getLaunchLatitude(); + private double launchLongitude = preferences.getLaunchLongitude(); private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL; - private boolean useISA = preferences.getBoolean(Preferences.LAUNCH_USE_ISA, true); - private double launchTemperature = preferences.getDouble(Preferences.LAUNCH_TEMPERATURE, ExtendedISAModel.STANDARD_TEMPERATURE); // In Kelvin - private double launchPressure = preferences.getDouble(Preferences.LAUNCH_PRESSURE, ExtendedISAModel.STANDARD_PRESSURE); // In Pascal + private boolean useISA = preferences.getISAAtmosphere(); + private double launchTemperature = preferences.getLaunchTemperature(); // In Kelvin + private double launchPressure = preferences.getLaunchPressure(); // In Pascal - private double timeStep = preferences.getDouble(Preferences.SIMULATION_TIME_STEP, RK4SimulationStepper.RECOMMENDED_TIME_STEP); + private double timeStep = preferences.getTimeStep(); private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP; private int randomSeed = new Random().nextInt(); From 5c6dc6796148e5eeaaa321fa7a6ecfa5bb7f0ce9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 22:11:20 +0200 Subject: [PATCH 17/76] Implement RASAero recovery device exporting --- .../rasaero/export/BodyTubeDTOAdapter.java | 3 + .../file/rasaero/export/RASAeroSaver.java | 12 + .../file/rasaero/export/RecoveryDTO.java | 229 ++++++++++++++++++ 3 files changed, 244 insertions(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index 8d6d4a960..392fc8fc6 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -5,6 +5,7 @@ import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.Parachute; import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; @@ -54,6 +55,8 @@ public interface BodyTubeDTOAdapter { warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2.", button.getName(), button.getInstanceCount())); } + } else if (child instanceof Parachute) { + // Do nothing, is handled by RecoveryDTO } else { warnings.add(String.format("Unsupported component '%s', ignoring.", child.getComponentName())); } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java index a2b372c93..ae3c1584c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -82,6 +82,7 @@ public class RASAeroSaver extends RocketSaver { RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); rad.setDesign(toRocketDesignDTO(doc.getRocket(), warnings, errors)); rad.setLaunchSite(toLaunchSiteDTO(doc, warnings, errors)); + rad.setRecovery(toRecoveryDTO(doc.getRocket(), warnings, errors)); rad.setSimulationList(toSimulationListDTO(doc, warnings, errors)); return rad; @@ -107,6 +108,17 @@ public class RASAeroSaver extends RocketSaver { return new LaunchSiteDTO(document, warnings, errors); } + /** + * Create RASAero recovery settings. + * @param rocket rocket to fetch the recovery devices from + * @param warnings list to add export warnings to + * @param errors list to add export errors to + * @return the RASAero launch site settings + */ + private RecoveryDTO toRecoveryDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { + return new RecoveryDTO(rocket, warnings, errors); + } + /** * Create a list of simulations. * @param document document that contains simulations diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java index ba79f415b..f0ffed438 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java @@ -1,12 +1,241 @@ package net.sf.openrocket.file.rasaero.export; +import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; +import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; +import net.sf.openrocket.rocketcomponent.Parachute; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.LinkedList; +import java.util.List; @XmlRootElement(name = RASAeroCommonConstants.RECOVERY) @XmlAccessorType(XmlAccessType.FIELD) public class RecoveryDTO { + @XmlElement(name = RASAeroCommonConstants.RECOVERY_ALTITUDE + 1) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double altitude1 = 0d; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_ALTITUDE + 2) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double altitude2 = 0d; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_DEVICE_TYPE + 1) + private String deviceType1 = "None"; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_DEVICE_TYPE + 2) + private String deviceType2 = "None"; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT + 1) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean event1 = false; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT + 2) + @XmlJavaTypeAdapter(CustomBooleanAdapter.class) + private Boolean event2 = false; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_SIZE + 1) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double size1 = 0d; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_SIZE + 2) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double size2 = 0d; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT_TYPE + 1) + private String eventType1 = "None"; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT_TYPE + 2) + private String eventType2 = "None"; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_CD + 1) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double CD1 = 0d; + @XmlElement(name = RASAeroCommonConstants.RECOVERY_CD + 2) + @XmlJavaTypeAdapter(CustomDoubleAdapter.class) + private Double CD2 = 0d; + + + @XmlTransient + private static final Logger log = LoggerFactory.getLogger(RecoveryDTO.class); + + /** + * We need a default, no-args constructor. + */ + public RecoveryDTO() { + } + + public RecoveryDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { + List parachutes = getParachutesFromRocket(rocket); + + switch (parachutes.size()) { + case 0: + log.debug("No parachutes present"); + break; + case 1: + configureRecoveryDevice1(parachutes.get(0), errors); + break; + case 2: + configureRecoveryDevice1(parachutes.get(0), errors); + configureRecoveryDevice2(parachutes.get(1), errors); + break; + } + } + + private List getParachutesFromRocket(Rocket rocket) { + List parachutes = new LinkedList<>(); + + for (int i = 0; i < Math.min(rocket.getChildCount(), 3); i++) { + AxialStage stage = (AxialStage) rocket.getChild(i); + + for (RocketComponent stageChild : stage.getChildren()) { + if (stageChild instanceof BodyTube) { + for (RocketComponent child : stageChild) { + if (child instanceof Parachute) { + parachutes.add((Parachute) child); + if (parachutes.size() == 2) { + return parachutes; + } + } + } + } + } + } + + return parachutes; + } + + private void configureRecoveryDevice1(Parachute device1, ErrorSet errors) { + setCD1(device1.getCD()); + setDeviceType1("Parachute"); + DeploymentConfiguration deployConfig = device1.getDeploymentConfigurations().getDefault(); + setAltitude1(deployConfig.getDeployAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); + if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.APOGEE) { + setEventType1(RASAeroCommonConstants.DEPLOYMENT_APOGEE); + } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { + setEventType1(RASAeroCommonConstants.RECOVERY_ALTITUDE); + } else { + errors.add(String.format("RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s'", + device1.getName(), deployConfig.getDeployEvent().toString())); + } + setEvent1(true); + setSize1(device1.getDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + + private void configureRecoveryDevice2(Parachute device2, ErrorSet errors) { + setCD1(device2.getCD()); + setDeviceType2("Parachute"); + DeploymentConfiguration deployConfig = device2.getDeploymentConfigurations().getDefault(); + setAltitude2(deployConfig.getDeployAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); + if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.APOGEE) { + setEventType2(RASAeroCommonConstants.DEPLOYMENT_APOGEE); + } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { + setEventType2(RASAeroCommonConstants.RECOVERY_ALTITUDE); + } else { + errors.add(String.format("RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s'", + device2.getName(), deployConfig.getDeployEvent().toString())); + } + setEvent2(true); + setSize2(device2.getDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + + public Double getAltitude1() { + return altitude1; + } + + public void setAltitude1(Double altitude1) { + this.altitude1 = altitude1; + } + + public Double getAltitude2() { + return altitude2; + } + + public void setAltitude2(Double altitude2) { + this.altitude2 = altitude2; + } + + public String getDeviceType1() { + return deviceType1; + } + + public void setDeviceType1(String deviceType1) { + this.deviceType1 = deviceType1; + } + + public String getDeviceType2() { + return deviceType2; + } + + public void setDeviceType2(String deviceType2) { + this.deviceType2 = deviceType2; + } + + public Boolean getEvent1() { + return event1; + } + + public void setEvent1(Boolean event1) { + this.event1 = event1; + } + + public Boolean getEvent2() { + return event2; + } + + public void setEvent2(Boolean event2) { + this.event2 = event2; + } + + public Double getSize1() { + return size1; + } + + public void setSize1(Double size1) { + this.size1 = size1; + } + + public Double getSize2() { + return size2; + } + + public void setSize2(Double size2) { + this.size2 = size2; + } + + public String getEventType1() { + return eventType1; + } + + public void setEventType1(String eventType1) { + this.eventType1 = eventType1; + } + + public String getEventType2() { + return eventType2; + } + + public void setEventType2(String eventType2) { + this.eventType2 = eventType2; + } + + public Double getCD1() { + return CD1; + } + + public void setCD1(Double CD1) { + this.CD1 = CD1; + } + + public Double getCD2() { + return CD2; + } + + public void setCD2(Double CD2) { + this.CD2 = CD2; + } } From 268d4f0ad820a6d55b93c87b5ed643b9da0833fa Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 22:11:37 +0200 Subject: [PATCH 18/76] Delete export file if unsuccessful --- swing/src/net/sf/openrocket/gui/main/BasicFrame.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 7a23aaf96..176702c00 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1416,8 +1416,12 @@ public class BasicFrame extends JFrame { } file = FileHelper.forceExtension(file, RASAeroCommonConstants.FILE_EXTENSION); - if (FileHelper.confirmWrite(file, this) ) { - return saveAsRASAero(file); + if (FileHelper.confirmWrite(file, this)) { + boolean result = saveAsRASAero(file); + if (!result) { + file.delete(); + } + return result; } return false; } From f9bfb1fe4364b85c5354fd106dc6c7b37f51a350 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 22:57:48 +0200 Subject: [PATCH 19/76] Use aggregate weight and CG for sim imports --- .../file/rasaero/export/SimulationDTO.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 95533d7f1..30aeff7d5 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -6,10 +6,12 @@ import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.masscalc.MassCalculator; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; @@ -133,19 +135,35 @@ public class SimulationDTO { StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(fcid); int stageNr = rocket.getChildPosition(stage); + FlightConfiguration CGCalcConfig = new FlightConfiguration(rocket); switch (stageNr) { // Sustainer case 0: setSustainerEngine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); setSustainerLaunchWt(stage.getSectionMass()); - setSustainerCG(stage.getCG().x); // TODO: use aggregate CG... + + // Calculate CG of sustainer + CGCalcConfig.setOnlyStage(0); + double sustainerCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + setSustainerCG(sustainerCG); + setSustainerIgnitionDelay(motorConfig.getIgnitionDelay()); break; // Booster 1 case 1: setBooster1Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); - setBooster1LaunchWt(stage.getSectionMass()); - setBooster1CG(stage.getCG().x); // TODO: use aggregate CG... + + // Aggregate mass of sustainer and booster 1 + setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass()); + + // Aggregate CG of sustainer and booster 1 + CGCalcConfig.setOnlyStage(0); + for (int i = 1; i <= stage.getStageNumber(); i++) { + CGCalcConfig._setStageActive(i, true); + } + double totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + setBooster1CG(totalCG); + setBooster1IgnitionDelay(motorConfig.getIgnitionDelay()); setBooster1SeparationDelay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) setIncludeBooster1(mount.isMotorMount()); @@ -153,8 +171,19 @@ public class SimulationDTO { // Booster 2 case 2: setBooster2Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); - setBooster2LaunchWt(stage.getSectionMass()); - setBooster2CG(stage.getCG().x); // TODO: use aggregate CG... + + // Aggregate mass of sustainer, booster 1 and booster 2 + setBooster2LaunchWt(rocket.getChild(0).getSectionMass() + rocket.getChild(1).getSectionMass() + + stage.getSectionMass()); + + // Calculate the aggregated CG of the sustainer, booster and booster 2 + CGCalcConfig.setOnlyStage(0); + for (int i = 1; i <= stage.getStageNumber(); i++) { + CGCalcConfig._setStageActive(i, true); + } + totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + setBooster2CG(totalCG); + setBooster2Delay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) setIncludeBooster2(mount.isMotorMount()); break; From b696e6f4b18f06132d7548c5a1d242d51cf09357 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 23:03:08 +0200 Subject: [PATCH 20/76] Check for tube children for motor mounts --- .../rasaero/export/SimulationListDTO.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java index 17dda78d1..76ff1e59c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -45,9 +45,26 @@ public class SimulationListDTO { continue; } for (RocketComponent stageChild : stage.getChildren()) { - // We can only really use body tubes as motor mounts, since other stuff (e.g. inner tube) is not supported in RASAero - if (stageChild instanceof BodyTube && ((BodyTube) stageChild).hasMotor()) { - mounts.put(stage, (BodyTube) stageChild); + if (stageChild instanceof BodyTube) { + // First check if the body tube itself has a motor + if (((BodyTube) stageChild).hasMotor()) { + mounts.put(stage, (BodyTube) stageChild); + break; + } + // Then check if it has an inner tube with a motor + else { + boolean addedMount = false; + for (RocketComponent tubeChild : stageChild.getChildren()) { + if (tubeChild instanceof MotorMount && ((MotorMount) tubeChild).hasMotor()) { + mounts.put(stage, (MotorMount) tubeChild); + addedMount = true; + break; + } + } + if (addedMount) { + break; + } + } } } } From 546e9b5ee8cf68cee306de77b190ec690bb34627 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 28 Mar 2023 23:09:34 +0200 Subject: [PATCH 21/76] Forgot conversions, doh! --- .../file/rasaero/RASAeroCommonConstants.java | 4 ++++ .../file/rasaero/export/SimulationDTO.java | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index c33965e6d..4482c6f0c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -200,6 +200,10 @@ public class RASAeroCommonConstants { * Angle conversion from OpenRocket units to RASAero units. RASAero is in degrees, OpenRocket in rad. */ public static final double OPENROCKET_TO_RASAERO_ANGLE = 180 / Math.PI; + /** + * Weight conversion from OpenRocket units to RASAero units. RASAero is in pounds (lb), OpenRocket in kilograms (kg). + */ + public static final double OPENROCKET_TO_RASAERO_WEIGHT = 2.20462262; /** * Temperature conversion from OpenRocket units to RASAero units. RASAero is in Fahrenheit, OpenRocket in Kelvin. */ diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 30aeff7d5..6d2fdd4b3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -140,12 +140,12 @@ public class SimulationDTO { // Sustainer case 0: setSustainerEngine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); - setSustainerLaunchWt(stage.getSectionMass()); + setSustainerLaunchWt(stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Calculate CG of sustainer CGCalcConfig.setOnlyStage(0); double sustainerCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; - setSustainerCG(sustainerCG); + setSustainerCG(sustainerCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setSustainerIgnitionDelay(motorConfig.getIgnitionDelay()); break; @@ -154,7 +154,8 @@ public class SimulationDTO { setBooster1Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); // Aggregate mass of sustainer and booster 1 - setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass()); + setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass() + * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Aggregate CG of sustainer and booster 1 CGCalcConfig.setOnlyStage(0); @@ -162,7 +163,7 @@ public class SimulationDTO { CGCalcConfig._setStageActive(i, true); } double totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; - setBooster1CG(totalCG); + setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBooster1IgnitionDelay(motorConfig.getIgnitionDelay()); setBooster1SeparationDelay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) @@ -174,7 +175,7 @@ public class SimulationDTO { // Aggregate mass of sustainer, booster 1 and booster 2 setBooster2LaunchWt(rocket.getChild(0).getSectionMass() + rocket.getChild(1).getSectionMass() + - stage.getSectionMass()); + stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Calculate the aggregated CG of the sustainer, booster and booster 2 CGCalcConfig.setOnlyStage(0); @@ -182,7 +183,7 @@ public class SimulationDTO { CGCalcConfig._setStageActive(i, true); } totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; - setBooster2CG(totalCG); + setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBooster2Delay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) setIncludeBooster2(mount.isMotorMount()); From dd344b261a77934a3a3ec620d2e613f323cb2464 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 31 Mar 2023 01:39:14 +0200 Subject: [PATCH 22/76] Don't show warning dialog if no warnings --- .../sf/openrocket/gui/main/BasicFrame.java | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 176702c00..97c0f0455 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1381,8 +1381,7 @@ public class BasicFrame extends JFrame { */ private File openFileSaveAsDialog(FileType fileType) { final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType); - - int option = chooser.showSaveDialog(BasicFrame.this); + int option = chooser.showSaveDialog(this); if (option != JFileChooser.APPROVE_OPTION) { log.info(Markers.USER_MARKER, "User decided not to save, option=" + option); @@ -1476,7 +1475,7 @@ public class BasicFrame extends JFrame { WarningSet warnings = ROCKET_SAVER.getWarnings(); ErrorSet errors = ROCKET_SAVER.getErrors(); - if (errors.isEmpty()) { + if (!warnings.isEmpty() && errors.isEmpty()) { WarningDialog.showWarnings(this, new Object[]{ // // The following problems were encountered while saving @@ -1486,7 +1485,7 @@ public class BasicFrame extends JFrame { }, // // Warnings while opening file trans.get("BasicFrame.WarningDialog.saving.title"), warnings); - } else { + } else if (!errors.isEmpty()) { ErrorWarningDialog.showErrorsAndWarnings(this, new Object[]{ // // The following problems were encountered while saving @@ -1604,26 +1603,11 @@ public class BasicFrame extends JFrame { * @return true if the file was saved, false otherwise */ private boolean saveAsAction() { - File file = null; - - final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, FileType.OPENROCKET); - - int option = chooser.showSaveDialog(BasicFrame.this); - - if (option != JFileChooser.APPROVE_OPTION) { - log.info(Markers.USER_MARKER, "User decided not to save, option=" + option); - return false; - } - - file = chooser.getSelectedFile(); + File file = openFileSaveAsDialog(FileType.OPENROCKET); if (file == null) { - log.info(Markers.USER_MARKER, "User did not select a file"); return false; } - ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); - chooser.storeOptions(document.getDefaultStorageOptions()); - file = FileHelper.forceExtension(file, "ork"); boolean result = FileHelper.confirmWrite(file, this) && saveAsOpenRocket(file); if (result) { From c2bdd38246b31eafc0a763404b7beb92bcd99cc3 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 31 Mar 2023 01:39:38 +0200 Subject: [PATCH 23/76] Clean up method --- .../gui/main/DesignFileSaveAsFileChooser.java | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java index 52a8d3c29..40547a092 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java @@ -5,6 +5,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; @@ -83,40 +84,53 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { } class RememberFilenamePropertyListener implements PropertyChangeListener { - private String oldFileName=null; + private String oldFileName = null; @Override - public void propertyChange(PropertyChangeEvent event){ - if( JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(event.getPropertyName())){ - if(null != event.getOldValue()){ - this.oldFileName = ((File)event.getOldValue()).getName(); + public void propertyChange(PropertyChangeEvent event) { + String propertyName = event.getPropertyName(); + + if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(propertyName)) { + handleSelectedFileChanged(event); + } else if (JFileChooser.FILE_FILTER_CHANGED_PROPERTY.equals(propertyName)) { + handleFileFilterChanged(event); + } + } + + private void handleSelectedFileChanged(PropertyChangeEvent event) { + if (event.getOldValue() != null) { + oldFileName = ((File) event.getOldValue()).getName(); + } + } + + private void handleFileFilterChanged(PropertyChangeEvent event) { + JFileChooser chooser = (JFileChooser) event.getSource(); + FileFilter currentFilter = chooser.getFileFilter(); + + if (currentFilter instanceof SimpleFileFilter) { + SimpleFileFilter filter = (SimpleFileFilter) currentFilter; + String desiredExtension = filter.getExtensions()[0]; + + if (oldFileName == null) { + return; } - }else if(JFileChooser.FILE_FILTER_CHANGED_PROPERTY.equals(event.getPropertyName())) { - JFileChooser chooser = (JFileChooser)event.getSource(); - if( chooser.getFileFilter() instanceof SimpleFileFilter) { - SimpleFileFilter filter = (SimpleFileFilter) (chooser.getFileFilter()); - String desiredExtension = filter.getExtensions()[0]; - - if (null == this.oldFileName) { - return; - } - String thisFileName = this.oldFileName; - - if (filter.accept(new File(thisFileName))) { - // nop - return; - } else { - String[] splitResults = thisFileName.split("\\."); - if (0 < splitResults.length) { - thisFileName = splitResults[0]; - } - chooser.setSelectedFile(new File(thisFileName + desiredExtension)); - return; - } + + String currentFileName = oldFileName; + + if (!filter.accept(new File(currentFileName))) { + currentFileName = removeExtension(currentFileName); + chooser.setSelectedFile(new File(currentFileName + desiredExtension)); } } } + private String removeExtension(String fileName) { + String[] splitResults = fileName.split("\\."); + if (splitResults.length > 0) { + return splitResults[0]; + } + return fileName; + } } From dead0ea75c5f9811a9ddf56d79a29e5306377f91 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 31 Mar 2023 01:50:24 +0200 Subject: [PATCH 24/76] Don't store accessory in separate var It doesn't need to be stored either, there is always a new instance creation, so... --- .../gui/main/DesignFileSaveAsFileChooser.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java index 40547a092..d4545a7d6 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java @@ -21,7 +21,6 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { private final FileType type; private final OpenRocketDocument document; - private final StorageOptionChooser storageChooser; private static final Translator trans = Application.getTranslator(); @@ -42,7 +41,7 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { case OPENROCKET: defaultFilename = FileHelper.forceExtension(defaultFilename,"ork"); this.setDialogTitle(trans.get("saveAs.openrocket.title")); - storageChooser = new StorageOptionChooser(document, document.getDefaultStorageOptions()); + StorageOptionChooser storageChooser = new StorageOptionChooser(document, document.getDefaultStorageOptions()); this.setAccessory(storageChooser); this.addChoosableFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER); this.setFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER); @@ -50,14 +49,12 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { case ROCKSIM: defaultFilename = FileHelper.forceExtension(defaultFilename,"rkt"); this.setDialogTitle(trans.get("saveAs.rocksim.title")); - storageChooser = null; this.addChoosableFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER); this.setFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER); break; case RASAERO: defaultFilename = FileHelper.forceExtension(defaultFilename,"CDX1"); this.setDialogTitle(trans.get("saveAs.rasaero.title")); - storageChooser = null; this.addChoosableFileFilter(FileHelper.RASAERO_DESIGN_FILTER); this.setFileFilter(FileHelper.RASAERO_DESIGN_FILTER); break; @@ -74,13 +71,6 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { this.setSelectedFile(defaultFilename); } } - - public void storeOptions(StorageOptions opts) { - if ( storageChooser != null ) { - storageChooser.storeOptions(opts); - } - } - } class RememberFilenamePropertyListener implements PropertyChangeListener { From c48c3b8e4e99ff6700c8317224177ab7ff7a84f0 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 31 Mar 2023 02:13:56 +0200 Subject: [PATCH 25/76] Fix some references to BasicFrame --- .../sf/openrocket/gui/main/BasicFrame.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 97c0f0455..83fea3e7c 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1381,7 +1381,7 @@ public class BasicFrame extends JFrame { */ private File openFileSaveAsDialog(FileType fileType) { final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType); - int option = chooser.showSaveDialog(this); + int option = chooser.showSaveDialog(BasicFrame.this); if (option != JFileChooser.APPROVE_OPTION) { log.info(Markers.USER_MARKER, "User decided not to save, option=" + option); @@ -1415,7 +1415,7 @@ public class BasicFrame extends JFrame { } file = FileHelper.forceExtension(file, RASAeroCommonConstants.FILE_EXTENSION); - if (FileHelper.confirmWrite(file, this)) { + if (FileHelper.confirmWrite(file, BasicFrame.this)) { boolean result = saveAsRASAero(file); if (!result) { file.delete(); @@ -1443,7 +1443,7 @@ public class BasicFrame extends JFrame { } }); panel.add(check); - int sel = JOptionPane.showOptionDialog(null, + int sel = JOptionPane.showOptionDialog(BasicFrame.this, panel, "", // title JOptionPane.OK_CANCEL_OPTION, @@ -1476,7 +1476,7 @@ public class BasicFrame extends JFrame { ErrorSet errors = ROCKET_SAVER.getErrors(); if (!warnings.isEmpty() && errors.isEmpty()) { - WarningDialog.showWarnings(this, + WarningDialog.showWarnings(BasicFrame.this, new Object[]{ // // The following problems were encountered while saving trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.", @@ -1486,7 +1486,7 @@ public class BasicFrame extends JFrame { // // Warnings while opening file trans.get("BasicFrame.WarningDialog.saving.title"), warnings); } else if (!errors.isEmpty()) { - ErrorWarningDialog.showErrorsAndWarnings(this, + ErrorWarningDialog.showErrorsAndWarnings(BasicFrame.this, new Object[]{ // // The following problems were encountered while saving trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.", @@ -1525,7 +1525,7 @@ public class BasicFrame extends JFrame { } file = FileHelper.forceExtension(file, "rkt"); - if (FileHelper.confirmWrite(file, this) ) { + if (FileHelper.confirmWrite(file, BasicFrame.this) ) { return saveAsRockSim(file); } return false; @@ -1551,7 +1551,7 @@ public class BasicFrame extends JFrame { } }); panel.add(check); - int sel = JOptionPane.showOptionDialog(null, + int sel = JOptionPane.showOptionDialog(BasicFrame.this, panel, "", // title JOptionPane.OK_CANCEL_OPTION, @@ -1609,7 +1609,7 @@ public class BasicFrame extends JFrame { } file = FileHelper.forceExtension(file, "ork"); - boolean result = FileHelper.confirmWrite(file, this) && saveAsOpenRocket(file); + boolean result = FileHelper.confirmWrite(file, BasicFrame.this) && saveAsOpenRocket(file); if (result) { MRUDesignFile opts = MRUDesignFile.getInstance(); opts.addFile(file.getAbsolutePath()); @@ -1629,7 +1629,7 @@ public class BasicFrame extends JFrame { file = FileHelper.forceExtension(file, "ork"); log.info("Saving document as " + file); - if (!StorageOptionChooser.verifyStorageOptions(document, this)) { + if (!StorageOptionChooser.verifyStorageOptions(document, BasicFrame.this)) { // User cancelled the dialog log.info(Markers.USER_MARKER, "User cancelled saving in storage options dialog"); return false; @@ -1638,7 +1638,7 @@ public class BasicFrame extends JFrame { document.getDefaultStorageOptions().setFileType(FileType.OPENROCKET); SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER); - if (!SwingWorkerDialog.runWorker(this, "Saving file", + if (!SwingWorkerDialog.runWorker(BasicFrame.this, "Saving file", "Writing " + file.getName() + "...", worker)) { // User cancelled the save @@ -1659,7 +1659,7 @@ public class BasicFrame extends JFrame { if (cause instanceof IOException) { log.warn("An I/O error occurred while saving " + file, cause); - JOptionPane.showMessageDialog(this, new String[] { + JOptionPane.showMessageDialog(BasicFrame.this, new String[] { "An I/O error occurred while saving:", e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); return false; @@ -1689,7 +1689,7 @@ public class BasicFrame extends JFrame { if (!document.isSaved()) { log.info("Confirming whether to save the design"); ComponentConfigDialog.disposeDialog(); - int result = JOptionPane.showConfirmDialog(this, + int result = JOptionPane.showConfirmDialog(BasicFrame.this, trans.get("BasicFrame.dlg.lbl1") + rocket.getName() + trans.get("BasicFrame.dlg.lbl2") + " " + trans.get("BasicFrame.dlg.lbl3"), @@ -1714,12 +1714,12 @@ public class BasicFrame extends JFrame { // Rocket has been saved or discarded log.debug("Disposing window"); - this.dispose(); + BasicFrame.this.dispose(); ComponentConfigDialog.disposeDialog(); ComponentAnalysisDialog.hideDialog(); - frames.remove(this); + frames.remove(BasicFrame.this); if (frames.isEmpty()) { // Don't quit the application on macOS, but keep the application open if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) { @@ -1733,13 +1733,13 @@ public class BasicFrame extends JFrame { } public void exportDecalAction() { - new ExportDecalDialog(this, document).setVisible(true); + new ExportDecalDialog(BasicFrame.this, document).setVisible(true); } public void printAction() { double rotation = rocketpanel.getFigure().getRotation(); - new PrintDialog(this, document, rotation).setVisible(true); + new PrintDialog(BasicFrame.this, document, rotation).setVisible(true); } /** From 51de605561104d90e21689ceb1b4dc4f9f31f603 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 31 Mar 2023 13:10:11 +0200 Subject: [PATCH 26/76] Add command line argument to ignore the JRE --- swing/src/net/sf/openrocket/startup/SwingStartup.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/startup/SwingStartup.java b/swing/src/net/sf/openrocket/startup/SwingStartup.java index 4f00838c5..3ee7c32f9 100644 --- a/swing/src/net/sf/openrocket/startup/SwingStartup.java +++ b/swing/src/net/sf/openrocket/startup/SwingStartup.java @@ -67,7 +67,8 @@ public class SwingStartup { log.info("Starting up OpenRocket version {}", BuildProperties.getVersion()); // Check JRE version - if (!checkJREVersion()) { + boolean ignoreJRE = System.getProperty("openrocket.ignore-jre") != null; + if (!ignoreJRE && !checkJREVersion()) { return; } From 027a7008a0ccabeee32375e81c16e7ed9065822d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 09:32:48 +0200 Subject: [PATCH 27/76] Refactor motors loader --- .../file/rasaero/{importt => }/RASAeroMotorsLoader.java | 2 +- .../sf/openrocket/file/rasaero/export/SimulationListDTO.java | 2 +- .../openrocket/file/rasaero/importt/SimulationListHandler.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename core/src/net/sf/openrocket/file/rasaero/{importt => }/RASAeroMotorsLoader.java (98%) diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java similarity index 98% rename from core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java rename to core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java index 53676d405..c1a986253 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroMotorsLoader.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.file.rasaero.importt; +package net.sf.openrocket.file.rasaero; import net.sf.openrocket.file.motor.GeneralMotorLoader; import net.sf.openrocket.logging.WarningSet; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java index 76ff1e59c..352e442aa 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -3,7 +3,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.file.rasaero.importt.RASAeroMotorsLoader; +import net.sf.openrocket.file.rasaero.RASAeroMotorsLoader; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.ThrustCurveMotor; diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index 234349bf3..ed6e90304 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -1,5 +1,6 @@ package net.sf.openrocket.file.rasaero.importt; +import net.sf.openrocket.file.rasaero.RASAeroMotorsLoader; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.DocumentLoadingContext; From b6af7220c6c6656e055fb828a8bda46fead5619b Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 09:35:31 +0200 Subject: [PATCH 28/76] Fill in missing warnings --- .../openrocket/file/rasaero/RASAeroCommonConstants.java | 9 ++++----- .../file/rasaero/export/BodyTubeDTOAdapter.java | 2 +- .../sf/openrocket/file/rasaero/export/BoosterDTO.java | 2 +- .../net/sf/openrocket/file/rasaero/export/FinDTO.java | 6 ++++-- .../openrocket/file/rasaero/export/RocketDesignDTO.java | 3 ++- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index 4482c6f0c..3d023f66f 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -312,7 +312,7 @@ public class RASAeroCommonConstants { } } - public static String OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(FinSet.CrossSection crossSection) { + public static String OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(FinSet.CrossSection crossSection, WarningSet warnings) { if (FinSet.CrossSection.SQUARE.equals(crossSection)) { return CROSS_SECTION_SQUARE; } else if (FinSet.CrossSection.ROUNDED.equals(crossSection)) { @@ -320,7 +320,7 @@ public class RASAeroCommonConstants { } else if (FinSet.CrossSection.AIRFOIL.equals(crossSection)) { return CROSS_SECTION_SUBSONIC_NACA; } else { - //TODO: warnings.add("Unknown fin cross section: " + crossSection + "."); + warnings.add("Unknown fin cross section: " + crossSection + "."); return null; } } @@ -350,7 +350,7 @@ public class RASAeroCommonConstants { } } - public static String OPENROCKET_TO_RASAERO_SURFACE(ExternalComponent.Finish finish) { + public static String OPENROCKET_TO_RASAERO_SURFACE(ExternalComponent.Finish finish, WarningSet warnings) { if (finish.equals(ExternalComponent.Finish.MIRROR)) { return FINISH_SMOOTH; } else if (finish.equals(ExternalComponent.Finish.FINISHPOLISHED)) { @@ -366,8 +366,7 @@ public class RASAeroCommonConstants { } else if (finish.equals(ExternalComponent.Finish.ROUGHUNFINISHED)) { return FINISH_CAST_IRON; } else { - // TODO - //warnings.add("Unknown surface finish: " + finish + ", defaulting to Smooth."); + warnings.add("Unknown surface finish: " + finish + ", defaulting to Smooth."); return FINISH_SMOOTH; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index 392fc8fc6..cc7fd0181 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -16,7 +16,7 @@ public interface BodyTubeDTOAdapter { default void applyBodyTubeSettings(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { for (RocketComponent child : bodyTube.getChildren()) { if (child instanceof TrapezoidFinSet) { - setFin(new FinDTO((TrapezoidFinSet) child)); + setFin(new FinDTO((TrapezoidFinSet) child, warnings, errors)); } else if (child instanceof LaunchLug) { if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe warnings.add(String.format("Already added a rail button, ignoring launch lug '%s'.", child.getName())); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index ba216c561..40ecd1837 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -165,7 +165,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet.", firstTube.getName(), stage.getName())); } - setFin(new FinDTO(finSet)); + setFin(new FinDTO(finSet, warnings, errors)); setPartType(RASAeroCommonConstants.BOOSTER); setLength(firstTube.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java index 8d65764a4..37113017c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -53,7 +55,7 @@ public class FinDTO { public FinDTO() { } - public FinDTO(TrapezoidFinSet fin) throws RASAeroExportException { + public FinDTO(TrapezoidFinSet fin, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { int finCount = fin.getFinCount(); if (finCount < 3 || finCount > 8) { throw new RASAeroExportException( @@ -66,7 +68,7 @@ public class FinDTO { setSpan(fin.getSpan() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setSweepDistance(fin.getSweep() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setThickness(fin.getThickness() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setAirfoilSection(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(fin.getCrossSection())); + setAirfoilSection(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(fin.getCrossSection(), warnings)); setLocation((-fin.getAxialOffset(AxialMethod.BOTTOM) + fin.getLength()) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index 8097e96d7..e92d2dc70 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -97,7 +97,8 @@ public class RocketDesignDTO { } addExternalPart(new NoseConeDTO((NoseCone) component, warnings, errors)); // Set the global surface finish to that of the first nose cone - setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish())); + setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish(), + warnings)); } else if (component instanceof Transition) { addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); } From a7c63260c57735cabcc5b01a24b75c027145107a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 09:41:02 +0200 Subject: [PATCH 29/76] Refactor RASAero motor conversion --- .../file/rasaero/RASAeroCommonConstants.java | 26 +++++++++++++++++ .../file/rasaero/export/SimulationDTO.java | 29 ++----------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index 3d023f66f..d639e4413 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.rasaero; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Manufacturer; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.FinSet; @@ -10,6 +12,7 @@ import net.sf.openrocket.util.Color; import net.sf.openrocket.util.MathUtil; import java.util.HashMap; +import java.util.List; import java.util.Map; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -371,6 +374,29 @@ public class RASAeroCommonConstants { } } + /** + * Format an OpenRocket motor as a RASAero motor. + * @param motors list of available RASAero motors + * @param ORMotor OpenRocket motor + * @return a RASAero String representation of a motor + */ + public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, WarningSet warnings) { + if (!(ORMotor instanceof ThrustCurveMotor)) { + return null; + } + + for (ThrustCurveMotor motor : motors) { + if (ORMotor.getDesignation().equals(motor.getDesignation()) && + ((ThrustCurveMotor) ORMotor).getManufacturer().matches(motor.getManufacturer().getDisplayName())) { + return motor.getDesignation() + + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; + } + } + + warnings.add(String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation())); + return null; + } + public static String OPENROCKET_TO_RASAERO_MANUFACTURER(Manufacturer manufacturer) { if (manufacturer.matches("AeroTech")) { return "AT"; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 6d2fdd4b3..a1ad035cd 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -139,7 +139,7 @@ public class SimulationDTO { switch (stageNr) { // Sustainer case 0: - setSustainerEngine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); setSustainerLaunchWt(stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Calculate CG of sustainer @@ -151,7 +151,7 @@ public class SimulationDTO { break; // Booster 1 case 1: - setBooster1Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); // Aggregate mass of sustainer and booster 1 setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass() @@ -171,7 +171,7 @@ public class SimulationDTO { break; // Booster 2 case 2: - setBooster2Engine(getRASAeroMotor(motors, motorConfig.getMotor(), warnings)); + setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); // Aggregate mass of sustainer, booster 1 and booster 2 setBooster2LaunchWt(rocket.getChild(0).getSectionMass() + rocket.getChild(1).getSectionMass() + @@ -196,29 +196,6 @@ public class SimulationDTO { } } - /** - * Format an OpenRocket motor as a RASAero motor. - * @param motors list of available RASAero motors - * @param ORMotor OpenRocket motor - * @return a RASAero String representation of a motor - */ - private String getRASAeroMotor(List motors, Motor ORMotor, WarningSet warnings) { - if (!(ORMotor instanceof ThrustCurveMotor)) { - return null; - } - - for (ThrustCurveMotor motor : motors) { - if (ORMotor.getDesignation().equals(motor.getDesignation()) && - ((ThrustCurveMotor) ORMotor).getManufacturer().matches(motor.getManufacturer().getDisplayName())) { - return motor.getDesignation() + - " (" + RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; - } - } - - warnings.add(String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation())); - return null; - } - public String getSustainerEngine() { return sustainerEngine; From 7cad04653de704daa5ea21a4ed2858f13698f317 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 09:51:04 +0200 Subject: [PATCH 30/76] Add 0-second delay suffic to RASAero motor export --- .../file/rasaero/RASAeroCommonConstants.java | 12 +++++++++--- .../file/rasaero/export/SimulationDTO.java | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index d639e4413..38e5526de 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -3,6 +3,7 @@ package net.sf.openrocket.file.rasaero; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.ExternalComponent; @@ -378,9 +379,11 @@ public class RASAeroCommonConstants { * Format an OpenRocket motor as a RASAero motor. * @param motors list of available RASAero motors * @param ORMotor OpenRocket motor + * @param motorConfig motor configuration of the selected motor * @return a RASAero String representation of a motor */ - public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, WarningSet warnings) { + public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, MotorConfiguration motorConfig, + WarningSet warnings) { if (!(ORMotor instanceof ThrustCurveMotor)) { return null; } @@ -388,8 +391,11 @@ public class RASAeroCommonConstants { for (ThrustCurveMotor motor : motors) { if (ORMotor.getDesignation().equals(motor.getDesignation()) && ((ThrustCurveMotor) ORMotor).getManufacturer().matches(motor.getManufacturer().getDisplayName())) { - return motor.getDesignation() + - " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; + String motorName = motor.getDesignation(); + if (motorConfig.getEjectionDelay() == 0) { + motorName += "-0"; + } + return motorName + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index a1ad035cd..4f0e6cd0b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -139,7 +139,7 @@ public class SimulationDTO { switch (stageNr) { // Sustainer case 0: - setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); + setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); setSustainerLaunchWt(stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Calculate CG of sustainer @@ -151,7 +151,7 @@ public class SimulationDTO { break; // Booster 1 case 1: - setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); + setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); // Aggregate mass of sustainer and booster 1 setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass() @@ -171,7 +171,7 @@ public class SimulationDTO { break; // Booster 2 case 2: - setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), warnings)); + setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); // Aggregate mass of sustainer, booster 1 and booster 2 setBooster2LaunchWt(rocket.getChild(0).getSectionMass() + rocket.getChild(1).getSectionMass() + From 153bd9ebf9aed90d46e6d28e581254ef9d101418 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 10:06:31 +0200 Subject: [PATCH 31/76] TODO: implement unit testing --- .../file/rasaero/export/RASAeroMotorExportTest.java | 6 ++++++ .../openrocket/file/rasaero/export/RASAeroSaverTest.java | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java create mode 100644 core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java diff --git a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java new file mode 100644 index 000000000..73c6aa403 --- /dev/null +++ b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java @@ -0,0 +1,6 @@ +package net.sf.openrocket.file.rasaero.export; + +public class RASAeroMotorExportTest { + // TODO: check correct name after export + // TODO: check 0-delay motors (should have -0 suffix) +} diff --git a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java new file mode 100644 index 000000000..e8c21510c --- /dev/null +++ b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java @@ -0,0 +1,7 @@ +package net.sf.openrocket.file.rasaero.export; + +public class RASAeroSaverTest { + // TODO: export a complex design + // TODO: check recovery + // TODO: check sims (including weights and CG) +} From 27a77825be0ccfd97a13e4d27f9e5b88e9421363 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 12:23:02 +0200 Subject: [PATCH 32/76] Fix issues in mass exporting --- .../file/rasaero/export/SimulationDTO.java | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 4f0e6cd0b..c295fdbcd 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -7,6 +7,7 @@ import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.masscalc.MassCalculator; +import net.sf.openrocket.masscalc.RigidBody; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.ThrustCurveMotor; @@ -124,6 +125,17 @@ public class SimulationDTO { return; } + if (mounts.isEmpty()) { + warnings.add(String.format("No motors found in simulation '%s', ignoring.", simulation.getName())); + return; + } + + // Get sustainer motor mass + MotorMount sustainerMount = mounts.get((AxialStage) rocket.getChild(0)); + MotorConfiguration sustainerConfig = sustainerMount.getMotorConfig(fcid); + Motor sustainerMotor = sustainerConfig.getMotor(); + double sustainerMotorMass = sustainerMotor != null ? sustainerMotor.getLaunchMass() : 0; + for (Map.Entry mountSet : mounts.entrySet()) { AxialStage stage = mountSet.getKey(); MotorMount mount = mountSet.getValue(); @@ -131,7 +143,10 @@ public class SimulationDTO { continue; } + // Get the motor info for this stage MotorConfiguration motorConfig = mount.getMotorConfig(fcid); + Motor motor = motorConfig.getMotor(); + double motorMass = motor != null ? motor.getLaunchMass() : 0; StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(fcid); int stageNr = rocket.getChildPosition(stage); @@ -139,8 +154,9 @@ public class SimulationDTO { switch (stageNr) { // Sustainer case 0: - setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); - setSustainerLaunchWt(stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); + setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); + double sustainerMass = stage.getSectionMass() + motorMass; + setSustainerLaunchWt(sustainerMass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Calculate CG of sustainer CGCalcConfig.setOnlyStage(0); @@ -151,18 +167,21 @@ public class SimulationDTO { break; // Booster 1 case 1: - setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); - - // Aggregate mass of sustainer and booster 1 - setBooster1LaunchWt(rocket.getChild(0).getSectionMass() + stage.getSectionMass() - * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); + setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); // Aggregate CG of sustainer and booster 1 CGCalcConfig.setOnlyStage(0); for (int i = 1; i <= stage.getStageNumber(); i++) { CGCalcConfig._setStageActive(i, true); } - double totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + RigidBody calc = MassCalculator.calculateStructure(CGCalcConfig); + + // Aggregate mass of sustainer and booster 1 + double booster1Mass = calc.getMass() + motorMass + sustainerMotorMass; + setBooster1LaunchWt(booster1Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); + + // CG + double totalCG = calc.getCM().x; setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBooster1IgnitionDelay(motorConfig.getIgnitionDelay()); @@ -171,18 +190,27 @@ public class SimulationDTO { break; // Booster 2 case 2: - setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motorConfig.getMotor(), motorConfig, warnings)); - - // Aggregate mass of sustainer, booster 1 and booster 2 - setBooster2LaunchWt(rocket.getChild(0).getSectionMass() + rocket.getChild(1).getSectionMass() + - stage.getSectionMass() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); + setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); // Calculate the aggregated CG of the sustainer, booster and booster 2 CGCalcConfig.setOnlyStage(0); for (int i = 1; i <= stage.getStageNumber(); i++) { CGCalcConfig._setStageActive(i, true); } - totalCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + calc = MassCalculator.calculateStructure(CGCalcConfig); + + // Get booster1 motor mass + MotorMount booster1Mount = mounts.get((AxialStage) rocket.getChild(1)); + MotorConfiguration booster1Config = booster1Mount.getMotorConfig(fcid); + Motor booster1Motor = booster1Config.getMotor(); + double booster1MotorMass = booster1Motor != null ? booster1Motor.getLaunchMass() : 0; + + // Aggregate mass of sustainer, booster 1 and booster 2 + double booster2Mass = calc.getMass() + motorMass + sustainerMotorMass + booster1MotorMass; + setBooster2LaunchWt(booster2Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); + + // CG + totalCG = calc.getCM().x; setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBooster2Delay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) From d0102fde0e169bd0e092c80b99e924b7d4f222eb Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 12:42:39 +0200 Subject: [PATCH 33/76] Still set mass/CG even if there is no motor --- .../file/rasaero/RASAeroCommonConstants.java | 2 +- .../file/rasaero/export/SimulationDTO.java | 55 +++++++++++++------ .../rasaero/export/SimulationListDTO.java | 6 ++ 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index 38e5526de..bcf9676a5 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -384,7 +384,7 @@ public class RASAeroCommonConstants { */ public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, MotorConfiguration motorConfig, WarningSet warnings) { - if (!(ORMotor instanceof ThrustCurveMotor)) { + if (!(ORMotor instanceof ThrustCurveMotor) || motorConfig == null) { return null; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index c295fdbcd..96eaad6b0 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -113,6 +113,7 @@ public class SimulationDTO { * @param rocket the rocket * @param simulation the simulation to convert * @param mounts a map of stages and their corresponding motor mount (only 1 mount per stage allowed) + * if a motor mount is null, it means that stage does not have any motors, but mass/CG export should still take place * @param motors a list of RASAero motors * @param warnings a list to add export warnings to * @param errors a list to add export errors to @@ -132,25 +133,36 @@ public class SimulationDTO { // Get sustainer motor mass MotorMount sustainerMount = mounts.get((AxialStage) rocket.getChild(0)); - MotorConfiguration sustainerConfig = sustainerMount.getMotorConfig(fcid); - Motor sustainerMotor = sustainerConfig.getMotor(); - double sustainerMotorMass = sustainerMotor != null ? sustainerMotor.getLaunchMass() : 0; + double sustainerMotorMass = 0; + if (sustainerMount != null) { + MotorConfiguration sustainerConfig = sustainerMount.getMotorConfig(fcid); + Motor sustainerMotor = sustainerConfig.getMotor(); + sustainerMotorMass = sustainerMotor != null ? sustainerMotor.getLaunchMass() : 0; + } for (Map.Entry mountSet : mounts.entrySet()) { AxialStage stage = mountSet.getKey(); MotorMount mount = mountSet.getValue(); - if (mount == null || stage == null) { + if (stage == null) { continue; } // Get the motor info for this stage - MotorConfiguration motorConfig = mount.getMotorConfig(fcid); - Motor motor = motorConfig.getMotor(); - double motorMass = motor != null ? motor.getLaunchMass() : 0; - StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(fcid); + MotorConfiguration motorConfig = mount != null ? mount.getMotorConfig(fcid) : null; + Motor motor = null; + StageSeparationConfiguration separationConfig = null; + double motorMass = 0; + if (motorConfig != null) { + motor = motorConfig.getMotor(); + motorMass = motor != null ? motor.getLaunchMass() : 0; + separationConfig = stage.getSeparationConfigurations().get(fcid); + } int stageNr = rocket.getChildPosition(stage); FlightConfiguration CGCalcConfig = new FlightConfiguration(rocket); + RigidBody calc; + double ignitionDelay, totalCG, separationDelay; + switch (stageNr) { // Sustainer case 0: @@ -163,7 +175,9 @@ public class SimulationDTO { double sustainerCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; setSustainerCG(sustainerCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setSustainerIgnitionDelay(motorConfig.getIgnitionDelay()); + // Ignition delay + ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; + setSustainerIgnitionDelay(ignitionDelay); break; // Booster 1 case 1: @@ -174,19 +188,25 @@ public class SimulationDTO { for (int i = 1; i <= stage.getStageNumber(); i++) { CGCalcConfig._setStageActive(i, true); } - RigidBody calc = MassCalculator.calculateStructure(CGCalcConfig); + calc = MassCalculator.calculateStructure(CGCalcConfig); // Aggregate mass of sustainer and booster 1 double booster1Mass = calc.getMass() + motorMass + sustainerMotorMass; setBooster1LaunchWt(booster1Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // CG - double totalCG = calc.getCM().x; + totalCG = calc.getCM().x; setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setBooster1IgnitionDelay(motorConfig.getIgnitionDelay()); - setBooster1SeparationDelay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) - setIncludeBooster1(mount.isMotorMount()); + // Ignition delay + ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; + setBooster1IgnitionDelay(ignitionDelay); + + // Separation delay + separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; + setBooster1SeparationDelay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) + + setIncludeBooster1(mount != null && mount.isMotorMount()); break; // Booster 2 case 2: @@ -213,8 +233,11 @@ public class SimulationDTO { totalCG = calc.getCM().x; setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setBooster2Delay(separationConfig.getSeparationDelay()); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) - setIncludeBooster2(mount.isMotorMount()); + // Separation delay + separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; + setBooster2Delay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) + + setIncludeBooster2(mount != null && mount.isMotorMount()); break; // Invalid default: diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java index 352e442aa..d41658275 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -67,6 +67,12 @@ public class SimulationListDTO { } } } + + // If at this point, we still don't have a mount, there is probably a mount without a motor. + // In that case, add a null mount, so that mass/CG export happens. + if (!mounts.containsKey(stage)) { + mounts.put(stage, null); + } } // Load all RASAero motors From 215080e0c95a19b404ba2ee4cf19b8be530a4e00 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 16:05:56 +0200 Subject: [PATCH 34/76] Create default sim if no sims present --- .../file/rasaero/export/SimulationDTO.java | 12 +++++++----- .../file/rasaero/export/SimulationListDTO.java | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 96eaad6b0..c5c8af6ab 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -120,14 +120,16 @@ public class SimulationDTO { */ public SimulationDTO(Rocket rocket, Simulation simulation, Map mounts, List motors, WarningSet warnings, ErrorSet errors) { - FlightConfigurationId fcid = simulation.getFlightConfigurationId(); - if (fcid == null) { - warnings.add(String.format("Empty simulation '%s', ignoring.", simulation.getName())); + String simulationName = simulation != null ? simulation.getName() : "DEFAULT"; + FlightConfigurationId fcid = simulation != null ? simulation.getFlightConfigurationId() : null; + + if (simulation != null && fcid == null) { + warnings.add(String.format("Empty simulation '%s', ignoring.", simulationName)); return; } if (mounts.isEmpty()) { - warnings.add(String.format("No motors found in simulation '%s', ignoring.", simulation.getName())); + warnings.add(String.format("No motors found in simulation '%s', ignoring.", simulationName)); return; } @@ -242,7 +244,7 @@ public class SimulationDTO { // Invalid default: errors.add(String.format("Invalid stage number '%d' for simulation '%s'", - stageNr, simulation.getName())); + stageNr, simulationName)); } } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java index d41658275..5eb30bd42 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java @@ -78,10 +78,16 @@ public class SimulationListDTO { // Load all RASAero motors List motors = RASAeroMotorsLoader.loadAllRASAeroMotors(warnings); + // Add all the simulations for (Simulation simulation : document.getSimulations()) { addSimulation(new SimulationDTO(rocket, simulation, mounts, motors, warnings, errors)); } + // If there are no simulations, add a default simulation (to have the mass/CG export) + if (document.getSimulations().size() == 0) { + addSimulation(new SimulationDTO(rocket, null, mounts, motors, warnings, errors)); + } + motors.clear(); } From d834deb5bac96aac42d63a8c294f297ac066f943 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 16:09:24 +0200 Subject: [PATCH 35/76] Refactor --- .../file/rasaero/export/SimulationDTO.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index c5c8af6ab..77fe360e2 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -169,52 +169,59 @@ public class SimulationDTO { // Sustainer case 0: setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); - double sustainerMass = stage.getSectionMass() + motorMass; + + // Calculate mass & CG of sustainer + CGCalcConfig.setOnlyStage(0); + calc = MassCalculator.calculateStructure(CGCalcConfig); + + // Set mass + double sustainerMass = calc.getMass() + motorMass; setSustainerLaunchWt(sustainerMass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - // Calculate CG of sustainer - CGCalcConfig.setOnlyStage(0); - double sustainerCG = MassCalculator.calculateStructure(CGCalcConfig).getCM().x; + // Set CG + double sustainerCG = calc.getCM().x; setSustainerCG(sustainerCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - // Ignition delay + // Set ignition delay ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; setSustainerIgnitionDelay(ignitionDelay); + break; // Booster 1 case 1: setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); - // Aggregate CG of sustainer and booster 1 + // Calculate mass & CG of sustainer + booster 1 combined CGCalcConfig.setOnlyStage(0); for (int i = 1; i <= stage.getStageNumber(); i++) { CGCalcConfig._setStageActive(i, true); } calc = MassCalculator.calculateStructure(CGCalcConfig); - // Aggregate mass of sustainer and booster 1 + // Set mass double booster1Mass = calc.getMass() + motorMass + sustainerMotorMass; setBooster1LaunchWt(booster1Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - // CG + // Set CG totalCG = calc.getCM().x; setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - // Ignition delay + // Set ignition delay ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; setBooster1IgnitionDelay(ignitionDelay); - // Separation delay + // Set separation delay separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; setBooster1SeparationDelay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) setIncludeBooster1(mount != null && mount.isMotorMount()); + break; // Booster 2 case 2: setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); - // Calculate the aggregated CG of the sustainer, booster and booster 2 + // Calculate mass & CG of sustainer + booster 1 + booster 2 combined CGCalcConfig.setOnlyStage(0); for (int i = 1; i <= stage.getStageNumber(); i++) { CGCalcConfig._setStageActive(i, true); @@ -227,19 +234,20 @@ public class SimulationDTO { Motor booster1Motor = booster1Config.getMotor(); double booster1MotorMass = booster1Motor != null ? booster1Motor.getLaunchMass() : 0; - // Aggregate mass of sustainer, booster 1 and booster 2 + // Set mass double booster2Mass = calc.getMass() + motorMass + sustainerMotorMass + booster1MotorMass; setBooster2LaunchWt(booster2Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - // CG + // Set CG totalCG = calc.getCM().x; setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - // Separation delay + // Set separation delay separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; setBooster2Delay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) setIncludeBooster2(mount != null && mount.isMotorMount()); + break; // Invalid default: From 4e2801ea4173029b4c2df8a4abafa3101bb8da99 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 16:45:51 +0200 Subject: [PATCH 36/76] Fix issues in CG exporting --- .../file/rasaero/export/SimulationDTO.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 77fe360e2..f64ca9bd5 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -135,10 +135,11 @@ public class SimulationDTO { // Get sustainer motor mass MotorMount sustainerMount = mounts.get((AxialStage) rocket.getChild(0)); + Motor sustainerMotor = null; double sustainerMotorMass = 0; if (sustainerMount != null) { MotorConfiguration sustainerConfig = sustainerMount.getMotorConfig(fcid); - Motor sustainerMotor = sustainerConfig.getMotor(); + sustainerMotor = sustainerConfig.getMotor(); sustainerMotorMass = sustainerMotor != null ? sustainerMotor.getLaunchMass() : 0; } @@ -179,7 +180,8 @@ public class SimulationDTO { setSustainerLaunchWt(sustainerMass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Set CG - double sustainerCG = calc.getCM().x; + double sustainerCG = calc.getCM().x; // = sutainer CG with no motors + sustainerCG = addMotorCGToStageCG(sustainerCG, calc.getMass(), mount, motor, fcid); setSustainerCG(sustainerCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); // Set ignition delay @@ -203,7 +205,9 @@ public class SimulationDTO { setBooster1LaunchWt(booster1Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Set CG - totalCG = calc.getCM().x; + totalCG = calc.getCM().x; // = sustainer + booster 1 CG with no sustainer & booster & motors + totalCG = addMotorCGToStageCG(totalCG, calc.getMass(), sustainerMount, sustainerMotor, fcid); + totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass, mount, motor, fcid); setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); // Set ignition delay @@ -239,7 +243,10 @@ public class SimulationDTO { setBooster2LaunchWt(booster2Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); // Set CG - totalCG = calc.getCM().x; + totalCG = calc.getCM().x; // CG of sustainer + booster 1 + booster 2 combined, with no sustainer, booster1 and booster2 motors! + totalCG = addMotorCGToStageCG(totalCG, calc.getMass(), sustainerMount, sustainerMotor, fcid); + totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass, booster1Mount, booster1Motor, fcid); + totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass + booster1MotorMass, mount, motor, fcid); setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); // Set separation delay @@ -257,6 +264,29 @@ public class SimulationDTO { } } + /** + * Combines the stage CG with the CG of the motor in that stage. + * @param stageCG The CG of the stage + * @param mount The motor mount of the stage + * @param motor The motor in the stage + * @return The combined CG + */ + private double addMotorCGToStageCG(double stageCG, double stageMass, MotorMount mount, Motor motor, FlightConfigurationId fcid) { + if (mount == null || !(motor instanceof ThrustCurveMotor)) { + return stageCG; + } + + // Calculate the motor CG + double motorPositionXRel = mount.getMotorPosition(fcid).x; // Motor position relative to the mount + double mountLocationX = mount.getLocations()[0].x; + double motorLocationX = mountLocationX + motorPositionXRel; + double motorCG = ((ThrustCurveMotor) motor).getCGPoints()[0].x + motorLocationX; + + double motorMass = motor.getLaunchMass(); + + return (stageCG*stageMass + motorCG*motorMass) / (stageMass + motorMass); + } + public String getSustainerEngine() { return sustainerEngine; From 518511bbd88f58f3c09c543c581829631ea0baa6 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 20:25:18 +0200 Subject: [PATCH 37/76] Fix issue in motor mount selection First of all: inefficient implementation Second of all: it could select children of body tubes, we don't want that --- .../rasaero/importt/SimulationListHandler.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index ed6e90304..6504bc7cd 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -12,6 +12,7 @@ import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; @@ -203,17 +204,18 @@ public class SimulationListHandler extends AbstractElementHandler { /** * Returns the furthest back motor mount in the stage. - * @param stage stage number + * @param stageNr stage number * @return furthest back motor mount of the stage */ - private MotorMount getMotorMountForStage(int stage) { - MotorMount mount = null; - for (RocketComponent component : rocket.getStage(stage)) { + private MotorMount getMotorMountForStage(int stageNr) { + AxialStage stage = (AxialStage) rocket.getChild(stageNr); + for (int i = stage.getChildCount() - 1; i > 0; i--) { + RocketComponent component = stage.getChild(i); if (component instanceof MotorMount) { - mount = (MotorMount) component; + return (MotorMount) component; } } - return mount; + return null; } } } From e7d9505ea9549544070aa7957b01a3a95d4dd1c1 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 1 Apr 2023 20:28:19 +0200 Subject: [PATCH 38/76] Add some log statements --- .../file/rasaero/RASAeroCommonConstants.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index bcf9676a5..ec955106c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -17,6 +17,8 @@ import java.util.List; import java.util.Map; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * List of constants used in RASAero files + helper functions to read parameters from it. @@ -228,6 +230,8 @@ public class RASAeroCommonConstants { RASAeroNoseConeShapeMap.put(SHAPE_ELLIPTICAL, Transition.Shape.ELLIPSOID); } + private static final Logger log = LoggerFactory.getLogger(RASAeroCommonConstants.class); + /** * Returns the OpenRocket nose cone shape from the RASAero shape string. * @param shape The RASAero shape string. @@ -311,7 +315,9 @@ public class RASAeroCommonConstants { } else if (CROSS_SECTION_SUBSONIC_NACA.equals(crossSection)) { return FinSet.CrossSection.AIRFOIL; } else { - warnings.add("Unknown fin cross section: " + crossSection + ", defaulting to Airfoil."); + String msg = "Unknown fin cross section: " + crossSection + ", defaulting to Airfoil."; + warnings.add(msg); + log.debug(msg); return FinSet.CrossSection.AIRFOIL; } } @@ -324,7 +330,9 @@ public class RASAeroCommonConstants { } else if (FinSet.CrossSection.AIRFOIL.equals(crossSection)) { return CROSS_SECTION_SUBSONIC_NACA; } else { - warnings.add("Unknown fin cross section: " + crossSection + "."); + String msg = "Unknown fin cross section: " + crossSection + "."; + warnings.add(msg); + log.warn(msg); return null; } } @@ -349,7 +357,9 @@ public class RASAeroCommonConstants { } else if (FINISH_CAST_IRON.equals(surfaceFinish)) { return ExternalComponent.Finish.ROUGHUNFINISHED; } else { - warnings.add("Unknown surface finish: " + surfaceFinish + ", defaulting to Regular Paint."); + String msg = "Unknown surface finish: " + surfaceFinish + ", defaulting to Regular Paint."; + warnings.add(msg); + log.debug(msg); return ExternalComponent.Finish.NORMAL; } } @@ -370,7 +380,9 @@ public class RASAeroCommonConstants { } else if (finish.equals(ExternalComponent.Finish.ROUGHUNFINISHED)) { return FINISH_CAST_IRON; } else { - warnings.add("Unknown surface finish: " + finish + ", defaulting to Smooth."); + String msg = "Unknown surface finish: " + finish + ", defaulting to Smooth."; + warnings.add(msg); + log.debug(msg); return FINISH_SMOOTH; } } @@ -385,6 +397,7 @@ public class RASAeroCommonConstants { public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, MotorConfiguration motorConfig, WarningSet warnings) { if (!(ORMotor instanceof ThrustCurveMotor) || motorConfig == null) { + log.debug("RASAero motor not found: not a thrust curve motor"); return null; } @@ -395,11 +408,14 @@ public class RASAeroCommonConstants { if (motorConfig.getEjectionDelay() == 0) { motorName += "-0"; } + log.debug(String.format("RASAero motor found: %s", motorName)); return motorName + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; } } - warnings.add(String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation())); + String msg = String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation()); + warnings.add(msg); + log.debug(msg); return null; } From 1852187da7d70a9bf8ce75d63f468a56a4b04969 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 3 Apr 2023 23:41:41 +0200 Subject: [PATCH 39/76] Aggregate same-sized body tubes in booster --- .../file/rasaero/export/BoosterDTO.java | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 40ecd1837..f162ba746 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -113,6 +113,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { !(firstChild instanceof Transition && !(firstChild instanceof NoseCone))) { throw new RASAeroExportException(String.format("First component of stage '%s' must be a body tube or transition.", stage.getName())); } + final BodyTube firstTube; if (firstChild instanceof Transition) { if (stage.getChildCount() == 1 || !(stage.getChild(1) instanceof BodyTube)) { @@ -144,22 +145,41 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setShoulderLength(firstChild.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setInsideDiameter(transition.getForeRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - - if (stage.getChildCount() > 2) { - warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components.", - stage.getName(), stage.getChildCount() - 2)); - } } else { firstTube = (BodyTube) stage.getChild(0); - if (stage.getChildCount() > 1) { - warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components.", - stage.getName(), stage.getChildCount() - 1)); + } + + TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); + + double tubeLength = firstTube.getLength(); + // Aggregate same-sized body tubes + for (int i = stage.getChildPosition(firstTube) + 1; i < stage.getChildCount(); i++) { + RocketComponent comp = stage.getChild(i); + if (comp instanceof BodyTube && + MathUtil.equals(((BodyTube) comp).getOuterRadius(), firstTube.getOuterRadius())) { + // Aggregate the tubes by combining the lengths + tubeLength += comp.getLength(); + + // If no fin set in firstTube, add fin from new tube + if (finSet == null) { + finSet = getFinSetFromBodyTube((BodyTube) comp); + } + } else { + // Case: normal body tube + if (stage.getChildPosition(firstTube) == 0) { + warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components.", + stage.getName(), stage.getChildCount() - i - 1)); + } + // Case: body tube with transition shoulder + else { + warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components.", + stage.getName(), stage.getChildCount() - i - 1)); + } } } applyBodyTubeSettings(firstTube, warnings, errors); - TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); if (finSet == null) { throw new RASAeroExportException( String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet.", @@ -168,7 +188,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setFin(new FinDTO(finSet, warnings, errors)); setPartType(RASAeroCommonConstants.BOOSTER); - setLength(firstTube.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setLength(tubeLength * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setLocation(firstChild.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(firstTube.getColor())); From 811305201fbdfd2519ec622d199c632429fbfdf9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 4 Apr 2023 00:00:16 +0200 Subject: [PATCH 40/76] Refactor WarningDialog list cell renderer --- .../gui/dialogs/ErrorWarningDialog.java | 39 ++----------------- .../openrocket/gui/dialogs/WarningDialog.java | 3 +- .../gui/util/BetterListCellRenderer.java | 36 +++++++++++++++++ 3 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java index f1b0834b7..335fe25b8 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java @@ -2,23 +2,21 @@ package net.sf.openrocket.gui.dialogs; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.components.StyledLabel; +import net.sf.openrocket.gui.util.BetterListCellRenderer; import net.sf.openrocket.logging.Error; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.Warning; import net.sf.openrocket.logging.WarningSet; -import javax.swing.DefaultListCellRenderer; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; -import javax.swing.JViewport; import javax.swing.ListSelectionModel; import java.awt.Color; import java.awt.Component; -import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -58,7 +56,7 @@ public abstract class ErrorWarningDialog { Warning[] w = warnings.toArray(new Warning[0]); final JList warningList = new JList<>(w); - warningList.setCellRenderer(new WarningListCellRenderer()); + warningList.setCellRenderer(new BetterListCellRenderer()); JScrollPane warningPane = new JScrollPane(warningList); content.add(warningPane, "wrap, growx"); @@ -78,50 +76,19 @@ public abstract class ErrorWarningDialog { } - private static class ErrorListCellRenderer extends DefaultListCellRenderer { + private static class ErrorListCellRenderer extends BetterListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - // Alternating row colors - if (!isSelected) { - if (index % 2 == 0) { - label.setBackground(Color.WHITE); - } else { - label.setBackground(new Color(245, 245, 245)); - } - } // Text color if (isSelected) { label.setForeground(Color.WHITE); } else { label.setForeground(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); } - return label; - } - } - private static class WarningListCellRenderer extends DefaultListCellRenderer { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - - // Alternating row colors - if (!isSelected) { - if (index % 2 == 0) { - label.setBackground(Color.WHITE); - } else { - label.setBackground(new Color(245, 245, 245)); - } - } - // Text color - if (isSelected) { - label.setForeground(Color.WHITE); - } else { - label.setForeground(Color.BLACK); - } return label; } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java index 8c8f72476..f856bd8a0 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java @@ -2,11 +2,11 @@ package net.sf.openrocket.gui.dialogs; import java.awt.Component; -import javax.swing.JDialog; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JScrollPane; +import net.sf.openrocket.gui.util.BetterListCellRenderer; import net.sf.openrocket.logging.Warning; import net.sf.openrocket.logging.WarningSet; @@ -17,6 +17,7 @@ public abstract class WarningDialog { Warning[] w = warnings.toArray(new Warning[0]); final JList list = new JList(w); + list.setCellRenderer(new BetterListCellRenderer()); JScrollPane pane = new JScrollPane(list); JOptionPane.showMessageDialog(parent, new Object[] { message, pane }, diff --git a/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java b/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java new file mode 100644 index 000000000..b8bbdd40f --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java @@ -0,0 +1,36 @@ +package net.sf.openrocket.gui.util; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JLabel; +import javax.swing.JList; +import java.awt.Color; +import java.awt.Component; + +/** + * An improved list cell renderer, with alternating row background colors. + * + * @author Sibo Van Gool + */ +public class BetterListCellRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + // Alternating row colors + if (!isSelected) { + if (index % 2 == 0) { + label.setBackground(Color.WHITE); + } else { + label.setBackground(new Color(245, 245, 245)); + } + } + // Text color + if (isSelected) { + label.setForeground(Color.WHITE); + } else { + label.setForeground(Color.BLACK); + } + return label; + } +} From 485c23161e5c6c921376d2729914301f0923d0cb Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 4 Apr 2023 00:04:02 +0200 Subject: [PATCH 41/76] Add warning when no motor --- .../sf/openrocket/file/rasaero/export/SimulationDTO.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index f64ca9bd5..f898722ec 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -162,10 +162,17 @@ public class SimulationDTO { } int stageNr = rocket.getChildPosition(stage); + // Add friendly reminder to user + if (motor == null) { + warnings.add(String.format("Stage %s has no motor.
" + + "  --> When adding a motor in RASAero, don't forget to update the stage mass and CG.", + stage.getName())); + } + + // Add the simulation info for each stage FlightConfiguration CGCalcConfig = new FlightConfiguration(rocket); RigidBody calc; double ignitionDelay, totalCG, separationDelay; - switch (stageNr) { // Sustainer case 0: From bcaf345bec5950dd164f49765dc0487839b1fc3a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 4 Apr 2023 14:14:41 +0200 Subject: [PATCH 42/76] Catch booster mount being null --- .../openrocket/file/rasaero/export/SimulationDTO.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index f898722ec..a27ec07a5 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -240,10 +240,14 @@ public class SimulationDTO { calc = MassCalculator.calculateStructure(CGCalcConfig); // Get booster1 motor mass + double booster1MotorMass = 0; MotorMount booster1Mount = mounts.get((AxialStage) rocket.getChild(1)); - MotorConfiguration booster1Config = booster1Mount.getMotorConfig(fcid); - Motor booster1Motor = booster1Config.getMotor(); - double booster1MotorMass = booster1Motor != null ? booster1Motor.getLaunchMass() : 0; + Motor booster1Motor = null; + if (booster1Mount != null) { + MotorConfiguration booster1Config = booster1Mount.getMotorConfig(fcid); + booster1Motor = booster1Config.getMotor(); + booster1MotorMass = booster1Motor != null ? booster1Motor.getLaunchMass() : 0; + } // Set mass double booster2Mass = calc.getMass() + motorMass + sustainerMotorMass + booster1MotorMass; From 0d1a5e84f824955ce1462c01cf037f86984e568c Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 4 Apr 2023 14:55:07 +0200 Subject: [PATCH 43/76] Move error messages to translation keys --- core/resources/l10n/messages.properties | 45 +++++++++++++++++++ .../file/rasaero/export/BasePartDTO.java | 14 +++--- .../file/rasaero/export/BodyTubeDTO.java | 14 +++--- .../rasaero/export/BodyTubeDTOAdapter.java | 24 +++++----- .../file/rasaero/export/BoosterDTO.java | 31 +++++++------ .../file/rasaero/export/FinDTO.java | 8 +++- .../file/rasaero/export/RecoveryDTO.java | 8 +++- .../file/rasaero/export/RocketDesignDTO.java | 16 ++++--- .../file/rasaero/export/SimulationDTO.java | 17 ++++--- .../file/rasaero/export/TransitionDTO.java | 12 +++-- 10 files changed, 137 insertions(+), 52 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 61f740dd0..7a64fe497 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1368,6 +1368,51 @@ saveAs.openrocket.title = Save as OpenRocket ork file saveAs.rocksim.title = Export as RockSim rkt file saveAs.rasaero.title = Export as RASAero CDX1 file +! RASAero exporting +RASAeroExport.warning1 = Nose cone may not be flipped. +RASAeroExport.warning2 = First component of booster must be body tube. +RASAeroExport.warning3 = Already added a rail button, ignoring launch lug '%s'. +RASAeroExport.warning4 = Already added a launch shoe, ignoring launch lug '%s'. +RASAeroExport.warning5 = Instance count of '%s' not set to 2, defaulting to 2 and adjusting launch lug length accordingly. +RASAeroExport.warning6 = Already added a launch lug, ignoring rail button '%s'. +RASAeroExport.warning7 = Already added a launch shoe, ignoring rail button '%s'. +RASAeroExport.warning8 = Instance count of '%s' equals %d, defaulting to 2. +RASAeroExport.warning9 = Unsupported component '%s', ignoring. +RASAeroExport.warning10 = Stage '%s' can only contain a body tube, ignoring other %d components. +RASAeroExport.warning11 = Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components. +RASAeroExport.warning12 = Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stages. +RASAeroExport.warning13 = Empty simulation '%s', ignoring. +RASAeroExport.warning14 = No motors found in simulation '%s', ignoring. +RASAeroExport.warning15 = Stage %s has no motor.
  --> When adding a motor in RASAero, don't forget to update the stage mass and CG. +RASAeroExport.error1 = Unsupported component: %s. +RASAeroExport.error2 = Length of '%s' must be greater than 0. +RASAeroExport.error3 = Diameter of '%s' must be greater than 0. +RASAeroExport.error4 = Launch lug diameter can not be 0. +RASAeroExport.error5 = Launch lug length can not be 0. +RASAeroExport.error6 = Rail button diameter can not be 0. +RASAeroExport.error7 = Rail button height can not be 0. +RASAeroExport.error8 = Launch shoe area can not be 0. +RASAeroExport.error9 = Invalid stage number '%d' for booster stage '%s'. +RASAeroExport.error10 = Stage '%s' may not be empty. +RASAeroExport.error11 = First component of stage '%s' must be a body tube or transition. +RASAeroExport.error12 = When the first component of stage '%s' is a transition, the second one must be a body tube. +RASAeroExport.error13 = No previous component for '%s' in stage '%s'. +RASAeroExport.error14 = Transition '%s' in stage '%s' must have the same fore radius as the aft radius of its previous component '%s'. +RASAeroExport.error15 = Radius of '%s' in stage '%s' must be the same as the aft radius of '%s'. +RASAeroExport.error16 = Body tube '%s' in stage '%s' must have a TrapezoidFinSet. +RASAeroExport.error17 = Length of '%s' must be greater than 0. +RASAeroExport.error18 = Diameter of '%s' must be greater than 0. +RASAeroExport.error19 = Inside diameter of '%s' must be greater than 0. +RASAeroExport.error20 = Fin set '%s' must have a fin count between 3 and 8. +RASAeroExport.error21 = RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s' +RASAeroExport.error22 = First component of the sustainer must be a nose cone. +RASAeroExport.error23 = Second component of the sustainer must be a body tube. +RASAeroExport.error24 = A nose cone can only be the first component of the rocket. +RASAeroExport.error25 = Invalid stage number '%d' for simulation '%s' +RASAeroExport.error26 = RASAero only supports conical transitions. +RASAeroExport.error27 = Transition '%s' has no previous component. +RASAeroExport.error28 = Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f. + ! SaveAsFileChooser SaveAsFileChooser.illegalFilename.title = Illegal filename SaveAsFileChooser.illegalFilename.message = The filename '%s' may not contain the character ' %c '. Please remove it. diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java index cc8bcf7f4..b72801c82 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -20,6 +21,7 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; /** @@ -49,6 +51,8 @@ public class BasePartDTO { private final WarningSet warnings; @XmlTransient private final ErrorSet errors; + @XmlTransient + private static final Translator trans = Application.getTranslator(); /** * We need a default no-args constructor. @@ -71,7 +75,7 @@ public class BasePartDTO { setPartType(RASAeroCommonConstants.NOSE_CONE); NoseCone noseCone = (NoseCone) component; if (noseCone.isFlipped()) { - throw new RASAeroExportException("Nose cone may not be flipped."); + throw new RASAeroExportException(trans.get("RASAeroExport.warning1")); } setDiameter(((NoseCone) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else if (component instanceof Transition) { @@ -83,11 +87,11 @@ public class BasePartDTO { setPartType(RASAeroCommonConstants.BOOSTER); AxialStage stage = (AxialStage) component; if (stage.getChildCount() == 0 || !(stage.getChild(0) instanceof BodyTube)) { - throw new RASAeroExportException("First component of booster must be body tube."); + throw new RASAeroExportException(trans.get("RASAeroExport.warning2")); } setDiameter(stage.getBoundingRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else { - throw new RASAeroExportException("Unsupported component: ." + component.getComponentName()); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error1"), component.getComponentName())); } setLength(component.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); @@ -109,7 +113,7 @@ public class BasePartDTO { public void setLength(Double length) throws RASAeroExportException { if (MathUtil.equals(length, 0)) { - throw new RASAeroExportException(String.format("Length of '%s' must be greater than 0.", component.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error2"), component.getName())); } this.length = length; } @@ -120,7 +124,7 @@ public class BasePartDTO { public void setDiameter(Double diameter) throws RASAeroExportException { if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0.", component.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error3"), component.getName())); } this.diameter = diameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java index 668eaf13f..382044398 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; @@ -15,6 +16,7 @@ import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; @XmlRootElement(name = RASAeroCommonConstants.BODY_TUBE) @@ -57,6 +59,8 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { private final WarningSet warnings; @XmlTransient private final ErrorSet errors; + @XmlTransient + private static final Translator trans = Application.getTranslator(); /** * We need a default no-args constructor. @@ -79,7 +83,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException { if (MathUtil.equals(launchLugDiameter, 0)) { - throw new RASAeroExportException("Launch lug diameter can not be 0."); + throw new RASAeroExportException(trans.get("RASAeroExport.error4")); } this.launchLugDiameter = launchLugDiameter; } @@ -90,7 +94,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException { if (MathUtil.equals(launchLugLength, 0)) { - throw new RASAeroExportException("Launch lug length can not be 0."); + throw new RASAeroExportException(trans.get("RASAeroExport.error5")); } this.launchLugLength = launchLugLength; } @@ -101,7 +105,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException { if (MathUtil.equals(railGuideDiameter, 0)) { - throw new RASAeroExportException("Rail button diameter can not be 0."); + throw new RASAeroExportException(trans.get("RASAeroExport.error6")); } this.railGuideDiameter = railGuideDiameter; } @@ -112,7 +116,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException { if (MathUtil.equals(railGuideHeight, 0)) { - throw new RASAeroExportException("Rail button height can not be 0."); + throw new RASAeroExportException(trans.get("RASAeroExport.error7")); } this.railGuideHeight = railGuideHeight; } @@ -123,7 +127,7 @@ public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { public void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException { if (MathUtil.equals(launchShoeArea, 0)) { - throw new RASAeroExportException("Launch shoe area can not be 0."); + throw new RASAeroExportException(trans.get("RASAeroExport.error8")); } this.launchShoeArea = launchShoeArea; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index cc7fd0181..31776816e 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.BodyTube; @@ -9,21 +10,27 @@ import net.sf.openrocket.rocketcomponent.Parachute; import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import javax.xml.bind.annotation.XmlTransient; + public interface BodyTubeDTOAdapter { + @XmlTransient + Translator trans = Application.getTranslator(); + default void applyBodyTubeSettings(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { for (RocketComponent child : bodyTube.getChildren()) { if (child instanceof TrapezoidFinSet) { setFin(new FinDTO((TrapezoidFinSet) child, warnings, errors)); } else if (child instanceof LaunchLug) { if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format("Already added a rail button, ignoring launch lug '%s'.", child.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning3"), child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format("Already added a launch shoe, ignoring launch lug '%s'.", child.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning4"), child.getName())); continue; } @@ -32,18 +39,16 @@ public interface BodyTubeDTOAdapter { if (lug.getInstanceCount() == 2) { setLaunchLugLength(lug.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } else { - warnings.add(String.format( - "Instance count of '%s' not set to 2, defaulting to 2 and adjusting launch lug length accordingly.", - lug.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning5"), lug.getName())); setLaunchLugLength(lug.getLength() * lug.getInstanceCount() / 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } } else if (child instanceof RailButton) { if (!MathUtil.equals(getLaunchLugDiameter(), 0) || !MathUtil.equals(getLaunchLugLength(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format("Already added a launch lug, ignoring rail button '%s'.", child.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning6"), child.getName())); continue; } if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format("Already added a launch shoe, ignoring rail button '%s'.", child.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning7"), child.getName())); continue; } @@ -52,13 +57,12 @@ public interface BodyTubeDTOAdapter { setRailGuideHeight(button.getTotalHeight() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); if (button.getInstanceCount() != 2) { - warnings.add(String.format("Instance count of '%s' equals %d, defaulting to 2.", - button.getName(), button.getInstanceCount())); + warnings.add(String.format(trans.get("RASAeroExport.warning8"), button.getName(), button.getInstanceCount())); } } else if (child instanceof Parachute) { // Do nothing, is handled by RecoveryDTO } else { - warnings.add(String.format("Unsupported component '%s', ignoring.", child.getComponentName())); + warnings.add(String.format(trans.get("RASAeroExport.warning9"), child.getComponentName())); } } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index f162ba746..b786171a3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -23,6 +24,7 @@ import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; @XmlRootElement(name = RASAeroCommonConstants.BOOSTER) @@ -83,6 +85,8 @@ public class BoosterDTO implements BodyTubeDTOAdapter { private final WarningSet warnings; @XmlTransient private final ErrorSet errors; + @XmlTransient + private static final Translator trans = Application.getTranslator(); /** @@ -101,44 +105,43 @@ public class BoosterDTO implements BodyTubeDTOAdapter { int stageNr = rocket.getChildPosition(stage); // Use this instead of stage.getStageNumber() in case there are parallel stages in the design if (stageNr != 1 && stageNr != 2) { - throw new RASAeroExportException(String.format("Invalid stage number '%d' for booster stage '%s'.", stageNr, stage.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error9"), stageNr, stage.getName())); } if (stage.getChildCount() == 0) { - throw new RASAeroExportException(String.format("Stage '%s' may not be empty.", stage.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error10"), stage.getName())); } RocketComponent firstChild = stage.getChild(0); if (!(firstChild instanceof BodyTube) && !(firstChild instanceof Transition && !(firstChild instanceof NoseCone))) { - throw new RASAeroExportException(String.format("First component of stage '%s' must be a body tube or transition.", stage.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error11"), stage.getName())); } final BodyTube firstTube; if (firstChild instanceof Transition) { if (stage.getChildCount() == 1 || !(stage.getChild(1) instanceof BodyTube)) { throw new RASAeroExportException( - String.format("When the first component of stage '%s' is a transition, the second one must be a body tube.", - stage.getName())); + String.format(trans.get("RASAeroExport.error12"), stage.getName())); } Transition transition = (Transition) firstChild; SymmetricComponent previousComponent = transition.getPreviousSymmetricComponent(); if (previousComponent == null) { - throw new RASAeroExportException(String.format("No previous component for '%s' in stage '%s'.", + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error13"), firstChild.getName(), stage.getName())); } if (!MathUtil.equals(transition.getForeRadius(), previousComponent.getAftRadius())) { throw new RASAeroExportException( - String.format("Transition '%s' in stage '%s' must have the same fore radius as the aft radius of its previous component '%s'.", + String.format(trans.get("RASAeroExport.error14"), transition.getName(), stage.getName(), previousComponent.getName())); } firstTube = (BodyTube) stage.getChild(1); if (!MathUtil.equals(firstTube.getOuterRadius(), transition.getAftRadius())) { throw new RASAeroExportException( - String.format("Radius of '%s' in stage '%s' must be the same as the aft radius of '%s'.", + String.format(trans.get("RASAeroExport.error15"), firstTube.getName(), stage.getName(), transition.getName())); } @@ -167,12 +170,12 @@ public class BoosterDTO implements BodyTubeDTOAdapter { } else { // Case: normal body tube if (stage.getChildPosition(firstTube) == 0) { - warnings.add(String.format("Stage '%s' can only contain a body tube, ignoring other %d components.", + warnings.add(String.format(trans.get("RASAeroExport.warning10"), stage.getName(), stage.getChildCount() - i - 1)); } // Case: body tube with transition shoulder else { - warnings.add(String.format("Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components.", + warnings.add(String.format(trans.get("RASAeroExport.warning11"), stage.getName(), stage.getChildCount() - i - 1)); } } @@ -182,7 +185,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { if (finSet == null) { throw new RASAeroExportException( - String.format("Body tube '%s' in stage '%s' must have a TrapezoidFinSet.", + String.format(trans.get("RASAeroExport.error16"), firstTube.getName(), stage.getName())); } setFin(new FinDTO(finSet, warnings, errors)); @@ -217,7 +220,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setLength(Double length) { if (MathUtil.equals(length, 0)) { - errors.add(String.format("Length of '%s' must be greater than 0.", component.getName())); + errors.add(String.format(trans.get("RASAeroExport.error17"), component.getName())); return; } this.length = length; @@ -229,7 +232,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setDiameter(Double diameter) throws RASAeroExportException { if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format("Diameter of '%s' must be greater than 0.", component.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error18"), component.getName())); } this.diameter = diameter; } @@ -240,7 +243,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { public void setInsideDiameter(Double insideDiameter) throws RASAeroExportException { if (MathUtil.equals(insideDiameter, 0)) { - throw new RASAeroExportException(String.format("Inside diameter of '%s' must be greater than 0.", component.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error19"), component.getName())); } this.insideDiameter = insideDiameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java index 37113017c..9668dbba3 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java @@ -2,16 +2,19 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; +import net.sf.openrocket.startup.Application; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement(name = RASAeroCommonConstants.FIN) @@ -49,6 +52,9 @@ public class FinDTO { @XmlJavaTypeAdapter(CustomDoubleAdapter.class) private Double FX3 = 0d; + @XmlTransient + private static final Translator trans = Application.getTranslator(); + /** * We need a default no-args constructor. */ @@ -59,7 +65,7 @@ public class FinDTO { int finCount = fin.getFinCount(); if (finCount < 3 || finCount > 8) { throw new RASAeroExportException( - String.format("Fin set '%s' must have a fin count between 3 and 8.", fin.getName())); + String.format(trans.get("RASAeroExport.error20"), fin.getName())); } setCount(fin.getFinCount()); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java index f0ffed438..790d11ede 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java @@ -3,6 +3,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -11,6 +12,7 @@ import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.Parachute; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,6 +64,8 @@ public class RecoveryDTO { @XmlTransient private static final Logger log = LoggerFactory.getLogger(RecoveryDTO.class); + @XmlTransient + private static final Translator trans = Application.getTranslator(); /** * We need a default, no-args constructor. @@ -119,7 +123,7 @@ public class RecoveryDTO { } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { setEventType1(RASAeroCommonConstants.RECOVERY_ALTITUDE); } else { - errors.add(String.format("RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s'", + errors.add(String.format(trans.get("RASAeroExport.error21"), device1.getName(), deployConfig.getDeployEvent().toString())); } setEvent1(true); @@ -136,7 +140,7 @@ public class RecoveryDTO { } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { setEventType2(RASAeroCommonConstants.RECOVERY_ALTITUDE); } else { - errors.add(String.format("RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s'", + errors.add(String.format(trans.get("RASAeroExport.error21"), device2.getName(), deployConfig.getDeployEvent().toString())); } setEvent2(true); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index e92d2dc70..b14929bdb 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -3,6 +3,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -11,6 +12,7 @@ import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.ArrayList; import javax.xml.bind.annotation.XmlAccessType; @@ -18,6 +20,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.util.List; @@ -67,10 +70,13 @@ public class RocketDesignDTO { @XmlElement(name = RASAeroCommonConstants.COMMENTS) private String comments = ""; - public RocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + @XmlTransient + private static final Translator trans = Application.getTranslator(); + + public RocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { setComments(rocket.getComment()); if (rocket.getChildCount() > 3) { - warnings.add("Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stages."); + warnings.add(trans.get("RASAeroExport.warning12")); } setUseBooster1(rocket.getStageCount() >= 2); setUseBooster2(rocket.getStageCount() == 3); @@ -82,17 +88,17 @@ public class RocketDesignDTO { try { RocketComponent component = sustainer.getChild(i); if (i == 0 && !(component instanceof NoseCone)) { - errors.add("First component of the sustainer must be a nose cone."); + errors.add(trans.get("RASAeroExport.error22")); return; } else if (i == 1 && !(component instanceof BodyTube)) { - errors.add("Second component of the sustainer must be a body tube."); + errors.add(trans.get("RASAeroExport.error23")); return; } if (component instanceof BodyTube) { addExternalPart(new BodyTubeDTO((BodyTube) component, warnings, errors)); } else if (component instanceof NoseCone) { if (i != 0) { - errors.add("A nose cone can only be the first component of the rocket."); + errors.add(trans.get("RASAeroExport.error24")); return; } addExternalPart(new NoseConeDTO((NoseCone) component, warnings, errors)); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index a27ec07a5..5a466c99e 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -4,6 +4,7 @@ import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.masscalc.MassCalculator; @@ -17,11 +18,13 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; +import net.sf.openrocket.startup.Application; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.util.List; import java.util.Map; @@ -102,6 +105,9 @@ public class SimulationDTO { @XmlJavaTypeAdapter(CustomDoubleAdapter.class) private Double optimumMaxAlt = 0d; + @XmlTransient + private static final Translator trans = Application.getTranslator(); + /** * We need a default, no-args constructor. */ @@ -124,12 +130,12 @@ public class SimulationDTO { FlightConfigurationId fcid = simulation != null ? simulation.getFlightConfigurationId() : null; if (simulation != null && fcid == null) { - warnings.add(String.format("Empty simulation '%s', ignoring.", simulationName)); + warnings.add(String.format(trans.get("RASAeroExport.warning13"), simulationName)); return; } if (mounts.isEmpty()) { - warnings.add(String.format("No motors found in simulation '%s', ignoring.", simulationName)); + warnings.add(String.format(trans.get("RASAeroExport.warning14"), simulationName)); return; } @@ -164,9 +170,7 @@ public class SimulationDTO { // Add friendly reminder to user if (motor == null) { - warnings.add(String.format("Stage %s has no motor.
" + - "  --> When adding a motor in RASAero, don't forget to update the stage mass and CG.", - stage.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning15"), stage.getName())); } // Add the simulation info for each stage @@ -269,8 +273,7 @@ public class SimulationDTO { break; // Invalid default: - errors.add(String.format("Invalid stage number '%d' for simulation '%s'", - stageNr, simulationName)); + errors.add(String.format(trans.get("RASAeroExport.error25"), stageNr, simulationName)); } } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java index a4a43e2fa..9b2d2bc36 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -2,6 +2,7 @@ package net.sf.openrocket.file.rasaero.export; import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.ErrorSet; import net.sf.openrocket.logging.WarningSet; @@ -9,11 +10,13 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; import java.util.Objects; @@ -26,6 +29,9 @@ public class TransitionDTO extends BasePartDTO { @XmlJavaTypeAdapter(CustomDoubleAdapter.class) private Double rearDiameter; + @XmlTransient + private static final Translator trans = Application.getTranslator(); + /** * We need a default no-args constructor. */ @@ -36,16 +42,16 @@ public class TransitionDTO extends BasePartDTO { super(transition, warnings, errors); if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { - throw new RASAeroExportException("RASAero only supports conical transitions."); + throw new RASAeroExportException(trans.get("RASAeroExport.error26")); } SymmetricComponent previousComp = transition.getPreviousSymmetricComponent(); if (previousComp == null) { - throw new RASAeroExportException(String.format("Transition '%s' has no previous component.", transition.getName())); + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error27"), transition.getName())); } if (!MathUtil.equals(transition.getForeRadius(), previousComp.getAftRadius())) { throw new RASAeroExportException( - String.format("Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f.", + String.format(trans.get("RASAeroExport.error28"), transition.getName(), previousComp.getAftRadius(), transition.getForeRadius())); } From 4f6b55364dca8c961dcd8f02c88510817dccf1e6 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 5 Apr 2023 23:29:11 +0200 Subject: [PATCH 44/76] Fix merge conflicts --- .../importt/RASAeroCommonConstants.java | 0 .../rasaero/importt/SimulationHandler.java | 54 +++++++++---------- .../importt/SimulationListHandler.java | 4 +- 3 files changed, 28 insertions(+), 30 deletions(-) delete mode 100644 core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroCommonConstants.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java index 4e9692b67..1bdff7c1a 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java @@ -1,11 +1,13 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.file.rasaero.RASAeroMotorsLoader; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; @@ -80,7 +82,7 @@ public class SimulationHandler extends AbstractElementHandler { } else if (RASAeroCommonConstants.SUSTAINER_LAUNCH_WT.equals(element)) { sustainerLaunchWt = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT; } else if (RASAeroCommonConstants.SUSTAINER_CG.equals(element)) { - sustainerCG = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + sustainerCG = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } else if (RASAeroCommonConstants.BOOSTER1_ENGINE.equals(element)) { booster1Engine = RASAeroMotorsLoader.getMotorFromRASAero(content, warnings); } else if (RASAeroCommonConstants.BOOSTER1_IGNITION_DELAY.equals(element)) { @@ -90,7 +92,7 @@ public class SimulationHandler extends AbstractElementHandler { } else if (RASAeroCommonConstants.BOOSTER1_LAUNCH_WT.equals(element)) { booster1LaunchWt = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT; } else if (RASAeroCommonConstants.BOOSTER1_CG.equals(element)) { - booster1CG = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + booster1CG = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } else if (RASAeroCommonConstants.INCLUDE_BOOSTER1.equals(element)) { includeBooster1 = Boolean.parseBoolean(content); } else if (RASAeroCommonConstants.BOOSTER2_ENGINE.equals(element)) { @@ -100,7 +102,7 @@ public class SimulationHandler extends AbstractElementHandler { } else if (RASAeroCommonConstants.BOOSTER2_LAUNCH_WT.equals(element)) { booster2LaunchWt = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT; } else if (RASAeroCommonConstants.BOOSTER2_CG.equals(element)) { - booster2CG = Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_LENGTH; + booster2CG = Double.parseDouble(content) / RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH; } else if (RASAeroCommonConstants.INCLUDE_BOOSTER2.equals(element)) { includeBooster2 = Boolean.parseBoolean(content); } @@ -116,15 +118,9 @@ public class SimulationHandler extends AbstractElementHandler { } // Add motors to the rocket - MotorMount sustainerMount = addMotorToStage(0, sustainerEngine, sustainerIgnitionDelay, fcid, warnings); - MotorMount booster1Mount = null; - if (includeBooster1) { - booster1Mount = addMotorToStage(1, booster1Engine, booster1IgnitionDelay, fcid, warnings); - } - MotorMount booster2Mount = null; - if (includeBooster2) { - booster2Mount = addMotorToStage(2, booster2Engine, 0.0, fcid, warnings); - } + MotorMount sustainerMount = addMotorToStage(0, sustainerEngine, sustainerIgnitionDelay, fcid, true, warnings); + MotorMount booster1Mount = addMotorToStage(1, booster1Engine, booster1IgnitionDelay, fcid, includeBooster1, warnings); + MotorMount booster2Mount = addMotorToStage(2, booster2Engine, 0.0, fcid, includeBooster2, warnings); // Set separation settings setSeparationDelay(0, 0.0, fcid); @@ -148,16 +144,17 @@ public class SimulationHandler extends AbstractElementHandler { } /** - * Adds a motor to the specified stage. - * @param stageNr The stage number - * @param motor The motor to add - * @param ignitionDelay The ignition delay - * @param id The flight configuration id + * Add a new motor to a stage + * @param stageNr number of the stage to add the motor to + * @param motor motor to add + * @param ignitionDelay ignition delay of the motor + * @param id flight config id to alter + * @param enableMotorMount whether the motor mount should be enabled or disabled * @param warnings The warning set - * @return The motor mount in which the motor is added, or null if no motor was added */ private MotorMount addMotorToStage(final int stageNr, final Motor motor, final Double ignitionDelay, - final FlightConfigurationId id, final WarningSet warnings) { + final FlightConfigurationId id, boolean enableMotorMount, + final WarningSet warnings) { if (motor == null || rocket.getStage(stageNr) == null) { return null; } @@ -183,6 +180,7 @@ public class SimulationHandler extends AbstractElementHandler { motorConfig.setIgnitionEvent(IgnitionEvent.AUTOMATIC); } mount.setMotorConfig(motorConfig, id); + mount.setMotorMount(enableMotorMount); return mount; } @@ -201,20 +199,18 @@ public class SimulationHandler extends AbstractElementHandler { /** * Returns the furthest back motor mount in the stage. - * @param stage stage number + * @param stageNr stage number * @return furthest back motor mount of the stage */ - private MotorMount getMotorMountForStage(int stage) { - MotorMount mount = null; - for (RocketComponent component : rocket.getStage(stage)) { + private MotorMount getMotorMountForStage(int stageNr) { + AxialStage stage = (AxialStage) rocket.getChild(stageNr); + for (int i = stage.getChildCount() - 1; i > 0; i--) { + RocketComponent component = stage.getChild(i); if (component instanceof MotorMount) { - mount = (MotorMount) component; + return (MotorMount) component; } } - if (mount != null) { - mount.setMotorMount(true); - } - return mount; + return null; } private void applyMassOverrides(WarningSet warnings) { diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java index a80e374f0..b06befb8b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationListHandler.java @@ -1,9 +1,11 @@ package net.sf.openrocket.file.rasaero.importt; -import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.file.rasaero.RASAeroMotorsLoader; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; +import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.SimulationOptions; import org.xml.sax.SAXException; From be7d9c4e54aa60eb2732c0c3863853ab1ea3666a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 6 Apr 2023 00:08:31 +0200 Subject: [PATCH 45/76] Fix ignore component count --- .../src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index b786171a3..e22acd5ab 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -171,12 +171,12 @@ public class BoosterDTO implements BodyTubeDTOAdapter { // Case: normal body tube if (stage.getChildPosition(firstTube) == 0) { warnings.add(String.format(trans.get("RASAeroExport.warning10"), - stage.getName(), stage.getChildCount() - i - 1)); + stage.getName(), stage.getChildCount() - i)); } // Case: body tube with transition shoulder else { warnings.add(String.format(trans.get("RASAeroExport.warning11"), - stage.getName(), stage.getChildCount() - i - 1)); + stage.getName(), stage.getChildCount() - i)); } } } From 6b4c18e0d8958118175cf27580381877becf2c9d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 6 Apr 2023 00:08:54 +0200 Subject: [PATCH 46/76] Export RASAero motor with designation from RASAero database --- .../file/motor/RASPMotorLoader.java | 26 +++++++++++---- .../file/rasaero/RASAeroCommonConstants.java | 24 +++++++------- .../file/rasaero/RASAeroMotorsLoader.java | 33 ++++++++++--------- .../file/rasaero/export/SimulationDTO.java | 6 ++-- 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java b/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java index 26c102f71..21aba82e5 100644 --- a/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java +++ b/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.motor; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; @@ -28,7 +30,10 @@ public class RASPMotorLoader extends AbstractMotorLoader { protected Charset getDefaultCharset() { return CHARSET; } - + + public List load(InputStream stream, String filename, boolean removeDelayFromDesignation) throws IOException { + return load(new InputStreamReader(stream, getDefaultCharset()), filename, removeDelayFromDesignation); + } /** * Load a Motor from a RASP file specified by the Reader. @@ -38,11 +43,11 @@ public class RASPMotorLoader extends AbstractMotorLoader { * is calculated from the thrust curve by assuming a constant exhaust velocity. * * @param reader the source of the file. + * @param removeDelayFromDesignation if true, the motor delay will be left out of the motor designation (e.g. "B6" instead of "B6-0") * @return a list of the {@link Motor} objects defined in the file. * @throws IOException if an I/O error occurs or if the file format is illegal. */ - @Override - public List load(Reader reader, String filename) throws IOException { + public List load(Reader reader, String filename, boolean removeDelayFromDesignation) throws IOException { List motors = new ArrayList<>(); BufferedReader in = new BufferedReader(reader); @@ -153,7 +158,7 @@ public class RASPMotorLoader extends AbstractMotorLoader { delayArray[i] = delays.get(i); } motors.add(createRASPMotor(manufacturer, designation, comment, - length, diameter, delayArray, propW, totalW, time, thrust)); + length, diameter, delayArray, propW, totalW, time, thrust, removeDelayFromDesignation)); } } catch (NumberFormatException e) { @@ -164,6 +169,11 @@ public class RASPMotorLoader extends AbstractMotorLoader { return motors; } + + @Override + public List load(Reader reader, String filename) throws IOException { + return load(reader, filename, true); + } /** @@ -172,7 +182,7 @@ public class RASPMotorLoader extends AbstractMotorLoader { */ private static ThrustCurveMotor.Builder createRASPMotor(String manufacturer, String designation, String comment, double length, double diameter, double[] delays, - double propW, double totalW, List time, List thrust) + double propW, double totalW, List time, List thrust, boolean removeDelayFromDesignation) throws IOException { // Add zero time/thrust if necessary @@ -188,8 +198,10 @@ public class RASPMotorLoader extends AbstractMotorLoader { thrustArray[i] = thrust.get(i); cgArray[i] = new Coordinate(length / 2, 0, 0, mass.get(i)); } - - designation = removeDelay(designation); + + if (removeDelayFromDesignation) { + designation = removeDelay(designation); + } // Create the motor digest from data available in RASP files MotorDigest motorDigest = new MotorDigest(); diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java index ec955106c..8c71fd2bf 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java @@ -1,5 +1,6 @@ package net.sf.openrocket.file.rasaero; +import net.sf.openrocket.file.motor.AbstractMotorLoader; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; @@ -389,27 +390,24 @@ public class RASAeroCommonConstants { /** * Format an OpenRocket motor as a RASAero motor. - * @param motors list of available RASAero motors + * @param RASAeroMotors list of available RASAero motors * @param ORMotor OpenRocket motor - * @param motorConfig motor configuration of the selected motor * @return a RASAero String representation of a motor */ - public static String OPENROCKET_TO_RASAERO_MOTOR(List motors, Motor ORMotor, MotorConfiguration motorConfig, + public static String OPENROCKET_TO_RASAERO_MOTOR(List RASAeroMotors, Motor ORMotor, WarningSet warnings) { - if (!(ORMotor instanceof ThrustCurveMotor) || motorConfig == null) { + if (!(ORMotor instanceof ThrustCurveMotor)) { log.debug("RASAero motor not found: not a thrust curve motor"); return null; } - for (ThrustCurveMotor motor : motors) { - if (ORMotor.getDesignation().equals(motor.getDesignation()) && - ((ThrustCurveMotor) ORMotor).getManufacturer().matches(motor.getManufacturer().getDisplayName())) { - String motorName = motor.getDesignation(); - if (motorConfig.getEjectionDelay() == 0) { - motorName += "-0"; - } - log.debug(String.format("RASAero motor found: %s", motorName)); - return motorName + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(motor.getManufacturer()) + ")"; + for (ThrustCurveMotor RASAeroMotor : RASAeroMotors) { + String RASAeroDesignation = AbstractMotorLoader.removeDelay(RASAeroMotor.getDesignation()); + if (ORMotor.getDesignation().equals(RASAeroDesignation) && + ((ThrustCurveMotor) ORMotor).getManufacturer().matches(RASAeroMotor.getManufacturer().getDisplayName())) { + String motorName = RASAeroMotor.getDesignation(); + log.debug(String.format("RASAero RASAeroMotor found: %s", motorName)); + return motorName + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(RASAeroMotor.getManufacturer()) + ")"; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java index e3665261b..43cc3ac75 100644 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java +++ b/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java @@ -1,6 +1,7 @@ package net.sf.openrocket.file.rasaero; import net.sf.openrocket.file.motor.GeneralMotorLoader; +import net.sf.openrocket.file.motor.RASPMotorLoader; import net.sf.openrocket.logging.WarningSet; import net.sf.openrocket.database.motor.ThrustCurveMotorSet; import net.sf.openrocket.file.motor.AbstractMotorLoader; @@ -69,23 +70,23 @@ public abstract class RASAeroMotorsLoader { public static List loadAllRASAeroMotors(WarningSet warnings) throws RuntimeException { List RASAeroMotors = new ArrayList<>(); - GeneralMotorLoader loader = new GeneralMotorLoader(); - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - String fileName = "RASAero_Motors.eng"; - InputStream is = classloader.getResourceAsStream("datafiles/thrustcurves/RASAero/" + fileName); - if (is == null) { - throw new RuntimeException("Could not find " + fileName); - } - try { - List motors = loader.load(is, fileName); - for (ThrustCurveMotor.Builder builder : motors) { - RASAeroMotors.add(builder.build()); - } - } catch (IOException e) { - warnings.add("Error during motor loading: " + e.getMessage()); - } + RASPMotorLoader loader = new RASPMotorLoader(); + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + String fileName = "RASAero_Motors.eng"; + InputStream is = classloader.getResourceAsStream("datafiles/thrustcurves/RASAero/" + fileName); + if (is == null) { + throw new RuntimeException("Could not find " + fileName); + } + try { + List motors = loader.load(is, fileName, false); + for (ThrustCurveMotor.Builder builder : motors) { + RASAeroMotors.add(builder.build()); + } + } catch (IOException e) { + warnings.add("Error during motor loading: " + e.getMessage()); + } - return RASAeroMotors; + return RASAeroMotors; } /** diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 5a466c99e..00df03c80 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -180,7 +180,7 @@ public class SimulationDTO { switch (stageNr) { // Sustainer case 0: - setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); + setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); // Calculate mass & CG of sustainer CGCalcConfig.setOnlyStage(0); @@ -202,7 +202,7 @@ public class SimulationDTO { break; // Booster 1 case 1: - setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); + setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); // Calculate mass & CG of sustainer + booster 1 combined CGCalcConfig.setOnlyStage(0); @@ -234,7 +234,7 @@ public class SimulationDTO { break; // Booster 2 case 2: - setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, motorConfig, warnings)); + setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); // Calculate mass & CG of sustainer + booster 1 + booster 2 combined CGCalcConfig.setOnlyStage(0); From 4da46d686941266020acceff854bc121b8af876d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 6 Apr 2023 00:10:40 +0200 Subject: [PATCH 47/76] Fix translation keys --- core/resources/l10n/messages.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 7a64fe497..71ebd9d86 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1378,9 +1378,9 @@ RASAeroExport.warning6 = Already added a launch lug, ignoring rail button '%s'. RASAeroExport.warning7 = Already added a launch shoe, ignoring rail button '%s'. RASAeroExport.warning8 = Instance count of '%s' equals %d, defaulting to 2. RASAeroExport.warning9 = Unsupported component '%s', ignoring. -RASAeroExport.warning10 = Stage '%s' can only contain a body tube, ignoring other %d components. -RASAeroExport.warning11 = Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d components. -RASAeroExport.warning12 = Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stages. +RASAeroExport.warning10 = Stage '%s' can only contain a body tube, ignoring other %d component(s). +RASAeroExport.warning11 = Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d component(s). +RASAeroExport.warning12 = Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stage(s). RASAeroExport.warning13 = Empty simulation '%s', ignoring. RASAeroExport.warning14 = No motors found in simulation '%s', ignoring. RASAeroExport.warning15 = Stage %s has no motor.
  --> When adding a motor in RASAero, don't forget to update the stage mass and CG. From c7aa226995b708e4a20f02d0cde3a1ff1d4f6968 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 6 Apr 2023 23:03:22 +0200 Subject: [PATCH 48/76] Add boattail export support --- core/resources/l10n/messages.properties | 2 ++ .../file/rasaero/export/BoattailDTO.java | 28 +++++++++++++++++++ .../rasaero/export/BodyTubeDTOAdapter.java | 4 +-- .../file/rasaero/export/BoosterDTO.java | 18 ++++++++++-- .../file/rasaero/export/RocketDesignDTO.java | 11 ++++++-- .../file/rasaero/export/TransitionDTO.java | 13 +++++++-- 6 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 71ebd9d86..69cec92ad 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1412,6 +1412,8 @@ RASAeroExport.error25 = Invalid stage number '%d' for simulation '%s' RASAeroExport.error26 = RASAero only supports conical transitions. RASAeroExport.error27 = Transition '%s' has no previous component. RASAeroExport.error28 = Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f. +RASAeroExport.error29 = Boattail length may not be zero. +RASAeroExport.error30 = Boattail rear diameter may not be zero. ! SaveAsFileChooser SaveAsFileChooser.illegalFilename.title = Illegal filename diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java new file mode 100644 index 000000000..bb28be19c --- /dev/null +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java @@ -0,0 +1,28 @@ +package net.sf.openrocket.file.rasaero.export; + +import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.rocketcomponent.Transition; +import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = RASAeroCommonConstants.BOATTAIL) +@XmlAccessorType(XmlAccessType.FIELD) +public class BoattailDTO extends TransitionDTO { + /** + * We need a default no-args constructor. + */ + public BoattailDTO() { + super(); + } + + public BoattailDTO(Transition boattail, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { + super(boattail, warnings, errors); + + setPartType(RASAeroCommonConstants.BOATTAIL); + } +} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java index 31776816e..2fd2e8870 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java @@ -89,11 +89,11 @@ public interface BodyTubeDTOAdapter { Double getBoattailLength(); - void setBoattailLength(Double boattailLength); + void setBoattailLength(Double boattailLength) throws RASAeroExportException; Double getBoattailRearDiameter(); - void setBoattailRearDiameter(Double boattailRearDiameter); + void setBoattailRearDiameter(Double boattailRearDiameter) throws RASAeroExportException; FinDTO getFin(); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index e22acd5ab..1080f9b1a 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -168,6 +168,14 @@ public class BoosterDTO implements BodyTubeDTOAdapter { finSet = getFinSetFromBodyTube((BodyTube) comp); } } else { + // If this booster is the last stage, and the last component is a transition, it could be a boattail + if (stageNr == rocket.getChildCount() - 1 && (comp instanceof Transition && !(comp instanceof NoseCone)) && + i == stage.getChildCount() - 1) { + Transition transition = (Transition) comp; + setBoattailLength(transition.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setBoattailRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + } + // Case: normal body tube if (stage.getChildPosition(firstTube) == 0) { warnings.add(String.format(trans.get("RASAeroExport.warning10"), @@ -324,7 +332,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { return boattailLength; } - public void setBoattailLength(Double boattailLength) { + public void setBoattailLength(Double boattailLength) throws RASAeroExportException { + if (boattailLength == 0) { + throw new RASAeroExportException(trans.get("RASAeroExport.error29")); + } this.boattailLength = boattailLength; } @@ -332,7 +343,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { return boattailRearDiameter; } - public void setBoattailRearDiameter(Double boattailRearDiameter) { + public void setBoattailRearDiameter(Double boattailRearDiameter) throws RASAeroExportException { + if (boattailRearDiameter == 0) { + throw new RASAeroExportException(trans.get("RASAeroExport.error30")); + } this.boattailRearDiameter = boattailRearDiameter; } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index b14929bdb..30a9e3938 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -78,8 +78,8 @@ public class RocketDesignDTO { if (rocket.getChildCount() > 3) { warnings.add(trans.get("RASAeroExport.warning12")); } - setUseBooster1(rocket.getStageCount() >= 2); - setUseBooster2(rocket.getStageCount() == 3); + setUseBooster1(rocket.getChildCount() >= 2); + setUseBooster2(rocket.getChildCount() == 3); AxialStage sustainer = rocket.getStage(0); @@ -106,7 +106,12 @@ public class RocketDesignDTO { setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish(), warnings)); } else if (component instanceof Transition) { - addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); + // If there is only a sustainer & this is the last child of the sustainer, it's a boattail + if (rocket.getChildCount() == 1 && (i == sustainer.getChildCount() - 1)) { + addExternalPart(new BoattailDTO((Transition) component, warnings, errors)); + } else { + addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); + } } } catch (RASAeroExportException e) { errors.add(e.getMessage()); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java index 9b2d2bc36..a54fc2dc9 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -10,6 +10,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @@ -19,10 +20,9 @@ import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; -import java.util.Objects; - @XmlRootElement(name = RASAeroCommonConstants.TRANSITION) @XmlAccessorType(XmlAccessType.FIELD) +@XmlSeeAlso({BoattailDTO.class}) public class TransitionDTO extends BasePartDTO { @XmlElement(name = RASAeroCommonConstants.REAR_DIAMETER) @@ -31,6 +31,8 @@ public class TransitionDTO extends BasePartDTO { @XmlTransient private static final Translator trans = Application.getTranslator(); + @XmlTransient + private static Transition component = null; /** * We need a default no-args constructor. @@ -41,6 +43,8 @@ public class TransitionDTO extends BasePartDTO { public TransitionDTO(Transition transition, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { super(transition, warnings, errors); + component = transition; + if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { throw new RASAeroExportException(trans.get("RASAeroExport.error26")); } @@ -62,7 +66,10 @@ public class TransitionDTO extends BasePartDTO { return rearDiameter; } - public void setRearDiameter(Double rearDiameter) { + public void setRearDiameter(Double rearDiameter) throws RASAeroExportException { + if (rearDiameter < 0.0001) { + throw new RASAeroExportException(String.format("'%s' rear diameter must be greater than 0.0001 inch", component)); + } this.rearDiameter = rearDiameter; } } From c6983323721d2c71451e292fed84e99c9f81f557 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 6 Apr 2023 23:21:57 +0200 Subject: [PATCH 49/76] Second component can also be boattail --- core/resources/l10n/messages.properties | 2 +- .../net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 69cec92ad..dc66d9ae5 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1406,7 +1406,7 @@ RASAeroExport.error19 = Inside diameter of '%s' must be greater than 0. RASAeroExport.error20 = Fin set '%s' must have a fin count between 3 and 8. RASAeroExport.error21 = RASAero only supports apogee and altitude deployment events for parachute '%s', not '%s' RASAeroExport.error22 = First component of the sustainer must be a nose cone. -RASAeroExport.error23 = Second component of the sustainer must be a body tube. +RASAeroExport.error23 = Second component of the sustainer must be a body tube (or boattail). RASAeroExport.error24 = A nose cone can only be the first component of the rocket. RASAeroExport.error25 = Invalid stage number '%d' for simulation '%s' RASAeroExport.error26 = RASAero only supports conical transitions. diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index 30a9e3938..d2a36ddda 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -90,7 +90,8 @@ public class RocketDesignDTO { if (i == 0 && !(component instanceof NoseCone)) { errors.add(trans.get("RASAeroExport.error22")); return; - } else if (i == 1 && !(component instanceof BodyTube)) { + } else if (i == 1 && !(component instanceof BodyTube || + (component instanceof Transition && !(component instanceof NoseCone) && (i == sustainer.getChildCount() - 1)))) { errors.add(trans.get("RASAeroExport.error23")); return; } From 11bed9142ef134cd129696dc4a55ec48bfce8ca8 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 7 Apr 2023 00:10:20 +0200 Subject: [PATCH 50/76] Fix order of XML properties --- .../file/rasaero/export/BasePartDTO.java | 1 + .../file/rasaero/export/BodyTubeDTO.java | 18 ++++++++++++++++++ .../file/rasaero/export/BoosterDTO.java | 2 +- .../file/rasaero/export/NoseConeDTO.java | 11 +++++++++++ .../file/rasaero/export/TransitionDTO.java | 9 +++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java index b72801c82..926e4ef90 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java @@ -30,6 +30,7 @@ import net.sf.openrocket.util.MathUtil; @XmlRootElement @XmlType(name="RASAeroBasePartDTO") @XmlAccessorType(XmlAccessType.FIELD) +@XmlTransient public class BasePartDTO { @XmlElement(name = RASAeroCommonConstants.PART_TYPE) private String partType; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java index 382044398..371c38d3c 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java @@ -13,6 +13,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -21,6 +22,23 @@ import net.sf.openrocket.util.MathUtil; @XmlRootElement(name = RASAeroCommonConstants.BODY_TUBE) @XmlAccessorType(XmlAccessType.FIELD) +@XmlType(propOrder = { + "partType", + "length", + "diameter", + "launchLugDiameter", + "launchLugLength", + "railGuideDiameter", + "railGuideHeight", + "launchShoeArea", + "location", + "color", + "boattailLength", + "boattailRearDiameter", + "boattailOffset", + "overhang", + "fin" +}) public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_DIAMETER) @XmlJavaTypeAdapter(CustomDoubleAdapter.class) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 1080f9b1a..0332d510f 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -67,7 +67,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { private Double shoulderLength; @XmlElement(name = RASAeroCommonConstants.NOZZLE_EXIT_DIAMETER) @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double nozzleExitDiameter; + private Double nozzleExitDiameter = 0d; @XmlElement(name = RASAeroCommonConstants.BOATTAIL_LENGTH) @XmlJavaTypeAdapter(CustomDoubleAdapter.class) private Double boattailLength; diff --git a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java index 95c3d5498..267741701 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java @@ -10,6 +10,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -17,6 +18,16 @@ import net.sf.openrocket.file.rasaero.RASAeroCommonConstants.NoseConeShapeSettin @XmlRootElement(name = RASAeroCommonConstants.NOSE_CONE) @XmlAccessorType(XmlAccessType.FIELD) +@XmlType(propOrder = { + "partType", + "length", + "diameter", + "shape", + "bluntRadius", + "location", + "color", + "powerLaw" +}) public class NoseConeDTO extends BasePartDTO { @XmlElement(name = RASAeroCommonConstants.SHAPE) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java index a54fc2dc9..26aa2bd16 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java @@ -12,6 +12,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; @@ -22,6 +23,14 @@ import net.sf.openrocket.util.MathUtil; @XmlRootElement(name = RASAeroCommonConstants.TRANSITION) @XmlAccessorType(XmlAccessType.FIELD) +@XmlType(propOrder = { + "partType", + "length", + "diameter", + "rearDiameter", + "location", + "color" +}) @XmlSeeAlso({BoattailDTO.class}) public class TransitionDTO extends BasePartDTO { From 99250aa62160d6c7dd4883164ba9d996e0966683 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 7 Apr 2023 14:45:04 +0200 Subject: [PATCH 51/76] Forgot to stop the loop --- core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 0332d510f..9b52bf818 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -186,6 +186,8 @@ public class BoosterDTO implements BodyTubeDTOAdapter { warnings.add(String.format(trans.get("RASAeroExport.warning11"), stage.getName(), stage.getChildCount() - i)); } + + break; } } From 24b35576788d87bdac80b6db61680a21c54cca87 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 7 Apr 2023 17:59:37 +0200 Subject: [PATCH 52/76] [#2183] Don't track stages when doing copy-ing --- .../rocketcomponent/RocketComponent.java | 106 ++++++++++++++---- .../gui/main/OpenRocketClipboard.java | 2 +- .../sf/openrocket/gui/main/RocketActions.java | 4 +- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 313022179..c662a976a 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1668,6 +1668,22 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab /////////// Children handling /////////// + /** + * Adds a child to the rocket component tree. The component is added to the end + * of the component's child list. This is a helper method that calls + * {@link #addChild(RocketComponent,int)}. + * + * @param component The component to add. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (add it to the stageList etc.) + * @throws IllegalArgumentException if the component is already part of some + * component tree. + * @see #addChild(RocketComponent,int) + */ + public final void addChild(RocketComponent component, boolean trackStage) { + checkState(); + addChild(component, children.size(), trackStage); + } + /** * Adds a child to the rocket component tree. The component is added to the end * of the component's child list. This is a helper method that calls @@ -1679,10 +1695,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @see #addChild(RocketComponent,int) */ public final void addChild(RocketComponent component) { - checkState(); - addChild(component, children.size()); + addChild(component, true); } - + /** * Adds a child to the rocket component tree. The component is added to * the given position of the component's child list. @@ -1692,28 +1707,29 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * * @param component The component to add. * @param index Position to add component to. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (add it to the stageList etc.) * @throws IllegalArgumentException If the component is already part of * some component tree. */ - public void addChild(RocketComponent component, int index) { + public void addChild(RocketComponent component, int index, boolean trackStage) { checkState(); - + if (component.parent != null) { throw new IllegalArgumentException("component " + component.getComponentName() + " is already in a tree"); } - + // Ensure that the no loops are created in component tree [A -> X -> Y -> B, B.addChild(A)] if (this.getRoot().equals(component)) { throw new IllegalStateException("Component " + component.getComponentName() + " is a parent of " + this.getComponentName() + ", attempting to create cycle in tree."); } - + if (!isCompatible(component)) { throw new IllegalStateException("Component: " + component.getComponentName() + " not currently compatible with component: " + getComponentName()); } - + children.add(index, component); component.parent = this; if (this.massOverridden && this.overrideSubcomponentsMass) { @@ -1746,18 +1762,48 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab child.CDOverriddenBy = component.CDOverriddenBy; } } - - if (component instanceof AxialStage) { + + if (trackStage && (component instanceof AxialStage)) { AxialStage nStage = (AxialStage) component; this.getRocket().trackStage(nStage); } - + this.checkComponentStructure(); component.checkComponentStructure(); - + fireAddRemoveEvent(component); } + /** + * Adds a child to the rocket component tree. The component is added to + * the given position of the component's child list. + *

+ * This method may be overridden to enforce more strict component addition rules. + * The tests should be performed first and then this method called. + * + * @param component The component to add. + * @param index Position to add component to. + * @throws IllegalArgumentException If the component is already part of + * some component tree. + */ + public void addChild(RocketComponent component, int index) { + addChild(component, index, true); + } + + /** + * Removes a child from the rocket component tree. + * (redirect to the removed-by-component + * + * @param n remove the n'th child. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (remove it to the stageList etc.) + * @throws IndexOutOfBoundsException if n is out of bounds + */ + public final void removeChild(int n, boolean trackStage) { + checkState(); + RocketComponent component = this.getChild(n); + this.removeChild(component, trackStage); + } + /** * Removes a child from the rocket component tree. * (redirect to the removed-by-component @@ -1766,9 +1812,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @throws IndexOutOfBoundsException if n is out of bounds */ public final void removeChild(int n) { - checkState(); - RocketComponent component = this.getChild(n); - this.removeChild(component); + removeChild(n, true); } /** @@ -1776,9 +1820,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * is not present as a child. * * @param component the component to remove + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (remove it to the stageList etc.) * @return whether the component was a child */ - public final boolean removeChild(RocketComponent component) { + public final boolean removeChild(RocketComponent component, boolean trackStage) { checkState(); component.checkComponentStructure(); @@ -1800,15 +1845,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab c.CDOverriddenBy = null; } } - - if (component instanceof AxialStage) { - AxialStage stage = (AxialStage) component; - this.getRocket().forgetStage(stage); - } - // Remove sub-stages of the removed component - for (AxialStage stage : component.getSubStages()) { - this.getRocket().forgetStage(stage); + if (trackStage) { + if (component instanceof AxialStage) { + AxialStage stage = (AxialStage) component; + this.getRocket().forgetStage(stage); + } + + // Remove sub-stages of the removed component + for (AxialStage stage : component.getSubStages()) { + this.getRocket().forgetStage(stage); + } } this.checkComponentStructure(); @@ -1821,6 +1868,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } return false; } + + /** + * Removes a child from the rocket component tree. Does nothing if the component + * is not present as a child. + * + * @param component the component to remove + * @return whether the component was a child + */ + public final boolean removeChild(RocketComponent component) { + return removeChild(component, true); + } diff --git a/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java b/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java index cda0a7e85..7302d8b43 100644 --- a/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java +++ b/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java @@ -101,7 +101,7 @@ public final class OpenRocketClipboard { if (someChildrenSelected) { for (RocketComponent child : component.getChildren()) { if (!clipboardComponents.contains(child)) { - component.removeChild(child); + component.removeChild(child, false); } else { clipboardComponents.remove(child); filterClipboardComponents(child.getChildren()); diff --git a/swing/src/net/sf/openrocket/gui/main/RocketActions.java b/swing/src/net/sf/openrocket/gui/main/RocketActions.java index 200244723..2bfa6e9a6 100644 --- a/swing/src/net/sf/openrocket/gui/main/RocketActions.java +++ b/swing/src/net/sf/openrocket/gui/main/RocketActions.java @@ -337,7 +337,7 @@ public class RocketActions { RocketComponent originalParent = components.get(i).getParent(); int originalParentIdx = components.indexOf(originalParent); - result.get(originalParentIdx).addChild(result.get(i)); + result.get(originalParentIdx).addChild(result.get(i), false); } else if (RocketComponent.listContainsParent(components, components.get(i))){ RocketComponent originalParent = components.get(i); while (originalParent != components.get(i)) { @@ -346,7 +346,7 @@ public class RocketActions { } } int originalParentIdx = components.indexOf(originalParent); - result.get(originalParentIdx).addChild(result.get(i)); + result.get(originalParentIdx).addChild(result.get(i), false); } } From 242345856f6b89a17534625681abd3f42df9e3c0 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 8 Apr 2023 00:24:28 +0200 Subject: [PATCH 53/76] Fix density in rail button database --- .../datafiles/components-openrocket/RailButton_Database.orc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swing/resources-src/datafiles/components-openrocket/RailButton_Database.orc b/swing/resources-src/datafiles/components-openrocket/RailButton_Database.orc index fcc33ea21..90fd9e0ba 100644 --- a/swing/resources-src/datafiles/components-openrocket/RailButton_Database.orc +++ b/swing/resources-src/datafiles/components-openrocket/RailButton_Database.orc @@ -6,13 +6,13 @@ - + Delrin 1420 BULK - + Nylon 1150 BULK From 731cd76ba06b092374e4fa0ef33a976ddd6effb1 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 8 Apr 2023 00:36:49 +0200 Subject: [PATCH 54/76] Add additional units in density units --- core/src/net/sf/openrocket/unit/UnitGroup.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/unit/UnitGroup.java b/core/src/net/sf/openrocket/unit/UnitGroup.java index c5e10cfdd..654410d70 100644 --- a/core/src/net/sf/openrocket/unit/UnitGroup.java +++ b/core/src/net/sf/openrocket/unit/UnitGroup.java @@ -212,6 +212,7 @@ public class UnitGroup { UNITS_DENSITY_BULK = new UnitGroup(); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "g/cm" + CUBED)); + UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000999, "kg/cm" + CUBED)); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "kg/dm" + CUBED)); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1, "kg/m" + CUBED)); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404, "oz/in" + CUBED)); @@ -220,13 +221,18 @@ public class UnitGroup { UNITS_DENSITY_SURFACE = new UnitGroup(); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10, "g/cm" + SQUARED)); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001, "g/m" + SQUARED)); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10000, "kg/cm" + SQUARED)); + UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(100, "kg/dm" + SQUARED)); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1, "kg/m" + SQUARED)); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418487, "oz/in" + SQUARED)); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.305151727, "oz/ft" + SQUARED)); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88242764, "lb/ft" + SQUARED)); UNITS_DENSITY_LINE = new UnitGroup(); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.1, "g/cm")); UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001, "g/m")); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(100, "kg/cm")); + UNITS_DENSITY_LINE.addUnit(new GeneralUnit(10, "kg/dm")); UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1, "kg/m")); UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465, "oz/ft")); @@ -428,7 +434,7 @@ public class UnitGroup { UNITS_ANGLE.setDefaultUnit(0); UNITS_DENSITY_BULK.setDefaultUnit(0); UNITS_DENSITY_SURFACE.setDefaultUnit(1); - UNITS_DENSITY_LINE.setDefaultUnit(0); + UNITS_DENSITY_LINE.setDefaultUnit(1); UNITS_FORCE.setDefaultUnit(0); UNITS_IMPULSE.setDefaultUnit(0); UNITS_TIME_STEP.setDefaultUnit(1); From 8cccf49a8fe0411c3acadfcdd7d805ef35268f8c Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 8 Apr 2023 23:43:56 +0200 Subject: [PATCH 55/76] Throw error when wrong component in booster stage --- core/resources/l10n/messages.properties | 12 ++++++------ .../file/rasaero/export/BoosterDTO.java | 19 ++++++++----------- .../file/rasaero/export/RocketDesignDTO.java | 2 +- .../file/rasaero/export/SimulationDTO.java | 6 +++--- .../rasaero/importt/SimulationHandler.java | 2 +- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index dc66d9ae5..2844bd5b6 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1378,12 +1378,10 @@ RASAeroExport.warning6 = Already added a launch lug, ignoring rail button '%s'. RASAeroExport.warning7 = Already added a launch shoe, ignoring rail button '%s'. RASAeroExport.warning8 = Instance count of '%s' equals %d, defaulting to 2. RASAeroExport.warning9 = Unsupported component '%s', ignoring. -RASAeroExport.warning10 = Stage '%s' can only contain a body tube, ignoring other %d component(s). -RASAeroExport.warning11 = Stage '%s' can only contain a body tube and transition shoulder, ignoring other %d component(s). -RASAeroExport.warning12 = Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stage(s). -RASAeroExport.warning13 = Empty simulation '%s', ignoring. -RASAeroExport.warning14 = No motors found in simulation '%s', ignoring. -RASAeroExport.warning15 = Stage %s has no motor.
  --> When adding a motor in RASAero, don't forget to update the stage mass and CG. +RASAeroExport.warning10 = Rocket should have no more then 3 stages (excl. boosters) in total.\nIgnoring other stage(s). +RASAeroExport.warning11 = Empty simulation '%s', ignoring. +RASAeroExport.warning12 = No motors found in simulation '%s', ignoring. +RASAeroExport.warning13 = Stage %s has no motor.
  --> When adding a motor in RASAero, don't forget to update the stage mass and CG. RASAeroExport.error1 = Unsupported component: %s. RASAeroExport.error2 = Length of '%s' must be greater than 0. RASAeroExport.error3 = Diameter of '%s' must be greater than 0. @@ -1414,6 +1412,8 @@ RASAeroExport.error27 = Transition '%s' has no previous component. RASAeroExport.error28 = Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f. RASAeroExport.error29 = Boattail length may not be zero. RASAeroExport.error30 = Boattail rear diameter may not be zero. +RASAeroExport.error31 = Stage '%s' can only contain a body tube (incl. shoulder transition), ignoring other %d component(s). +RASAeroExport.error32 = Boattails can only be added to the last stage. ! SaveAsFileChooser SaveAsFileChooser.illegalFilename.title = Illegal filename diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 9b52bf818..3cba7be79 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -169,24 +169,21 @@ public class BoosterDTO implements BodyTubeDTOAdapter { } } else { // If this booster is the last stage, and the last component is a transition, it could be a boattail - if (stageNr == rocket.getChildCount() - 1 && (comp instanceof Transition && !(comp instanceof NoseCone)) && - i == stage.getChildCount() - 1) { + boolean isBoattail = (comp instanceof Transition && !(comp instanceof NoseCone)) && i == stage.getChildCount() - 1; + if (stageNr == rocket.getChildCount() - 1 && isBoattail) { Transition transition = (Transition) comp; setBoattailLength(transition.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBoattailRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } - // Case: normal body tube - if (stage.getChildPosition(firstTube) == 0) { - warnings.add(String.format(trans.get("RASAeroExport.warning10"), - stage.getName(), stage.getChildCount() - i)); - } - // Case: body tube with transition shoulder - else { - warnings.add(String.format(trans.get("RASAeroExport.warning11"), - stage.getName(), stage.getChildCount() - i)); + String msg = String.format(trans.get("RASAeroExport.error31"), stage.getName(), stage.getChildCount() - i); + + if (isBoattail) { + msg = "" + msg + "
 " + trans.get("RASAeroExport.error32") + ""; } + errors.add(msg); + break; } } diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index d2a36ddda..1b6fe6367 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -76,7 +76,7 @@ public class RocketDesignDTO { public RocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { setComments(rocket.getComment()); if (rocket.getChildCount() > 3) { - warnings.add(trans.get("RASAeroExport.warning12")); + warnings.add(trans.get("RASAeroExport.warning10")); } setUseBooster1(rocket.getChildCount() >= 2); setUseBooster2(rocket.getChildCount() == 3); diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java index 00df03c80..b21a715b0 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java @@ -130,12 +130,12 @@ public class SimulationDTO { FlightConfigurationId fcid = simulation != null ? simulation.getFlightConfigurationId() : null; if (simulation != null && fcid == null) { - warnings.add(String.format(trans.get("RASAeroExport.warning13"), simulationName)); + warnings.add(String.format(trans.get("RASAeroExport.warning11"), simulationName)); return; } if (mounts.isEmpty()) { - warnings.add(String.format(trans.get("RASAeroExport.warning14"), simulationName)); + warnings.add(String.format(trans.get("RASAeroExport.warning12"), simulationName)); return; } @@ -170,7 +170,7 @@ public class SimulationDTO { // Add friendly reminder to user if (motor == null) { - warnings.add(String.format(trans.get("RASAeroExport.warning15"), stage.getName())); + warnings.add(String.format(trans.get("RASAeroExport.warning13"), stage.getName())); } // Add the simulation info for each stage diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java index 1bdff7c1a..730f730ea 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/SimulationHandler.java @@ -160,7 +160,7 @@ public class SimulationHandler extends AbstractElementHandler { } MotorMount mount = getMotorMountForStage(stageNr); if (mount == null) { - warnings.add("No motor mount found for stage " + stageNr + ". Ignoring motor."); + warnings.add("No motor mount found for stage " + stageNr + ". Ignoring motor."); return null; } MotorConfiguration motorConfig = new MotorConfiguration(mount, id); From fa9613923f3d12e5df1591abf84189a1a75364c1 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 8 Apr 2023 23:46:11 +0200 Subject: [PATCH 56/76] Check for invalid component in sustainer --- core/resources/l10n/messages.properties | 1 + .../net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 2844bd5b6..0abbc4f4a 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1414,6 +1414,7 @@ RASAeroExport.error29 = Boattail length may not be zero. RASAeroExport.error30 = Boattail rear diameter may not be zero. RASAeroExport.error31 = Stage '%s' can only contain a body tube (incl. shoulder transition), ignoring other %d component(s). RASAeroExport.error32 = Boattails can only be added to the last stage. +RASAeroExport.error33 = Invalid component '%s' in sustainer stage. ! SaveAsFileChooser SaveAsFileChooser.illegalFilename.title = Illegal filename diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java index 1b6fe6367..c9dd0c63b 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java @@ -113,6 +113,8 @@ public class RocketDesignDTO { } else { addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); } + } else { + throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error33"), component.getComponentName())); } } catch (RASAeroExportException e) { errors.add(e.getMessage()); From d3faa950b3289f5fdd5708ab9627d1452c7022a9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 8 Apr 2023 23:51:11 +0200 Subject: [PATCH 57/76] Fix translation key --- core/resources/l10n/messages.properties | 2 +- core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 0abbc4f4a..d9183914e 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1412,7 +1412,7 @@ RASAeroExport.error27 = Transition '%s' has no previous component. RASAeroExport.error28 = Transition '%s' should have the same fore radius as the aft radius (%f) of its previous component, not %f. RASAeroExport.error29 = Boattail length may not be zero. RASAeroExport.error30 = Boattail rear diameter may not be zero. -RASAeroExport.error31 = Stage '%s' can only contain a body tube (incl. shoulder transition), ignoring other %d component(s). +RASAeroExport.error31 = Stage '%s' can only contain a body tube (incl. shoulder transition). RASAeroExport.error32 = Boattails can only be added to the last stage. RASAeroExport.error33 = Invalid component '%s' in sustainer stage. diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 3cba7be79..968c519de 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -176,7 +176,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { setBoattailRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); } - String msg = String.format(trans.get("RASAeroExport.error31"), stage.getName(), stage.getChildCount() - i); + String msg = String.format(trans.get("RASAeroExport.error31"), stage.getName()); if (isBoattail) { msg = "" + msg + "
 " + trans.get("RASAeroExport.error32") + ""; From 0e2f357c79001b2d98433beb2625e9167c3727c4 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sun, 9 Apr 2023 09:26:32 +0200 Subject: [PATCH 58/76] Fix boattail issue --- core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index 968c519de..d0a0db009 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -174,6 +174,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { Transition transition = (Transition) comp; setBoattailLength(transition.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); setBoattailRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + break; } String msg = String.format(trans.get("RASAeroExport.error31"), stage.getName()); From e47d6481b26e0dfff28ada029b9d4b665a8e313d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 10 Apr 2023 23:44:12 +0200 Subject: [PATCH 59/76] [#2083] Account for uom in material database loader --- core/src/net/sf/openrocket/preset/xml/MaterialDTO.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java index 6694dbb7d..7ce9c468d 100644 --- a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java +++ b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java @@ -10,6 +10,7 @@ import javax.xml.bind.annotation.XmlRootElement; import net.sf.openrocket.database.Databases; import net.sf.openrocket.material.Material; +import net.sf.openrocket.unit.Unit; import net.sf.openrocket.util.Chars; /** @@ -98,6 +99,13 @@ public class MaterialDTO { if (uom != null) { uom = uom.replace('2', Chars.SQUARED); uom = uom.replace('3', Chars.CUBED); + if (type != null) { + // The density value is stored in the XML file in the units of measure, but OR expects the density to be + // in SI units, so we need to convert it to SI units + Unit uomUnit = type.getORMaterialType().getUnitGroup().getUnit(getUom()); + density = uomUnit.fromUnit(density); + //type.getORMaterialType().getUnitGroup().setDefaultUnit(uomUnit); + } } } From dab6ec3642544020fcc03eb04ddde097078d24a8 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 10 Apr 2023 23:46:32 +0200 Subject: [PATCH 60/76] Update dbcook database --- swing/resources-src/datafiles/components-dbcook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swing/resources-src/datafiles/components-dbcook b/swing/resources-src/datafiles/components-dbcook index 1aa03bb4a..827509c8e 160000 --- a/swing/resources-src/datafiles/components-dbcook +++ b/swing/resources-src/datafiles/components-dbcook @@ -1 +1 @@ -Subproject commit 1aa03bb4a44a145f459939f487159a202dbf0f9f +Subproject commit 827509c8eaa27e456095ffe57009c53aa6ee4f28 From b57ad3d39393c592073c623df9a7896b8a2c8445 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 10 Apr 2023 23:52:47 +0200 Subject: [PATCH 61/76] Undo WIP --- core/src/net/sf/openrocket/preset/xml/MaterialDTO.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java index 7ce9c468d..745dd8acb 100644 --- a/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java +++ b/core/src/net/sf/openrocket/preset/xml/MaterialDTO.java @@ -45,7 +45,6 @@ public class MaterialDTO { density = theDensity; type = theType; uom = theUom; - type.getORMaterialType().getUnitGroup().setDefaultUnit(getUom()); } public String getName() { @@ -70,7 +69,6 @@ public class MaterialDTO { public void setType(final MaterialTypeDTO theType) { type = theType; - type.getORMaterialType().getUnitGroup().setDefaultUnit(getUom()); } public String getUom() { @@ -79,7 +77,6 @@ public class MaterialDTO { public void setUom(final String theUom) { uom = theUom; - type.getORMaterialType().getUnitGroup().setDefaultUnit(theUom); } Material asMaterial() { @@ -104,7 +101,6 @@ public class MaterialDTO { // in SI units, so we need to convert it to SI units Unit uomUnit = type.getORMaterialType().getUnitGroup().getUnit(getUom()); density = uomUnit.fromUnit(density); - //type.getORMaterialType().getUnitGroup().setDefaultUnit(uomUnit); } } } From ea29038b112f7e9965b1c50d75e5bb5b19ac4e49 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 11 Apr 2023 16:29:19 +0200 Subject: [PATCH 62/76] Documentation Co-authored-by: hcraigmiller <68821492+hcraigmiller@users.noreply.github.com> --- .../net/sf/openrocket/file/rasaero/export/RASAeroSaver.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java index ae3c1584c..cdd902397 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java @@ -18,6 +18,12 @@ import java.io.OutputStreamWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; +/** + * This class is responsible for marshalling an OpenRocketDocument (OR design) to RASAero-compliant XML. + * Big thanks to hcraigmiller for testing and providing feedback. + * + * @author Sibo Van Gool + */ public class RASAeroSaver extends RocketSaver { /** * The logger. From 884959a98e1ef55dddc9537ffd39e4e70a105db7 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 11 Apr 2023 16:34:22 +0200 Subject: [PATCH 63/76] Need at least one runnable unit test --- .../file/rasaero/export/RASAeroMotorExportTest.java | 7 +++++++ .../openrocket/file/rasaero/export/RASAeroSaverTest.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java index 73c6aa403..52d827241 100644 --- a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java +++ b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroMotorExportTest.java @@ -1,6 +1,13 @@ package net.sf.openrocket.file.rasaero.export; +import org.junit.Test; + public class RASAeroMotorExportTest { // TODO: check correct name after export // TODO: check 0-delay motors (should have -0 suffix) + + @Test + public void dummy() { + // We need at least one runnable test method + } } diff --git a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java index e8c21510c..3736c6dad 100644 --- a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java +++ b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java @@ -1,7 +1,14 @@ package net.sf.openrocket.file.rasaero.export; +import org.junit.Test; + public class RASAeroSaverTest { // TODO: export a complex design // TODO: check recovery // TODO: check sims (including weights and CG) + + @Test + public void dummy() { + // We need at least one runnable test method + } } From 68904b597a9cf1191609384d5b5fde38a7a88cb3 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 11 Apr 2023 22:08:44 +0200 Subject: [PATCH 64/76] Fix wrong fin location referencing in aggregate tubes --- .../sf/openrocket/file/rasaero/export/BoosterDTO.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java index d0a0db009..632926d66 100644 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java @@ -155,6 +155,7 @@ public class BoosterDTO implements BodyTubeDTOAdapter { TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); double tubeLength = firstTube.getLength(); + double finLocationOffset = 0; // Aggregate same-sized body tubes for (int i = stage.getChildPosition(firstTube) + 1; i < stage.getChildCount(); i++) { RocketComponent comp = stage.getChild(i); @@ -167,6 +168,11 @@ public class BoosterDTO implements BodyTubeDTOAdapter { if (finSet == null) { finSet = getFinSetFromBodyTube((BodyTube) comp); } + // We need an offset to the fin location, since the fin axial offset is referenced to its parent tube, + // which can be different from the bottom of the aggregate tubes + else { + finLocationOffset += comp.getLength(); + } } else { // If this booster is the last stage, and the last component is a transition, it could be a boattail boolean isBoattail = (comp instanceof Transition && !(comp instanceof NoseCone)) && i == stage.getChildCount() - 1; @@ -196,7 +202,10 @@ public class BoosterDTO implements BodyTubeDTOAdapter { String.format(trans.get("RASAeroExport.error16"), firstTube.getName(), stage.getName())); } - setFin(new FinDTO(finSet, warnings, errors)); + FinDTO finDTO = new FinDTO(finSet, warnings, errors); + double finLocation = finDTO.getLocation(); + finDTO.setLocation(finLocation + finLocationOffset * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); + setFin(finDTO); setPartType(RASAeroCommonConstants.BOOSTER); setLength(tubeLength * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); From c5843b97ea4d2378a43fb422c746729738555d2a Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 12 Apr 2023 22:02:07 +0200 Subject: [PATCH 65/76] Add some basic RASAero export unit tests --- .../file/rasaero/importt/RASAeroLoader.java | 2 +- .../file/rasaero/export/01.One-stage.ork | Bin 0 -> 105186 bytes .../file/rasaero/export/02.Two-stage.ork | Bin 0 -> 149443 bytes .../file/rasaero/export/03.Three-stage.ork | Bin 0 -> 89559 bytes .../file/rasaero/export/RASAeroSaverTest.java | 229 +++++++++++++++++- 5 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 core/test/net/sf/openrocket/file/rasaero/export/01.One-stage.ork create mode 100644 core/test/net/sf/openrocket/file/rasaero/export/02.Two-stage.ork create mode 100644 core/test/net/sf/openrocket/file/rasaero/export/03.Three-stage.ork diff --git a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroLoader.java b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroLoader.java index f26197ae8..cc1ba60be 100644 --- a/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroLoader.java +++ b/core/src/net/sf/openrocket/file/rasaero/importt/RASAeroLoader.java @@ -27,7 +27,7 @@ public class RASAeroLoader extends AbstractRocketLoader { * if an error occurs during loading. */ @Override - protected void loadFromStream(DocumentLoadingContext context, InputStream source, String fileName) throws IOException, RocketLoadException { + public void loadFromStream(DocumentLoadingContext context, InputStream source, String fileName) throws IOException, RocketLoadException { InputSource xmlSource = new InputSource(source); RASAeroHandler handler = new RASAeroHandler(context, fileName); diff --git a/core/test/net/sf/openrocket/file/rasaero/export/01.One-stage.ork b/core/test/net/sf/openrocket/file/rasaero/export/01.One-stage.ork new file mode 100644 index 0000000000000000000000000000000000000000..629c6b95a2841a55dc2d6c53913c825295bc671f GIT binary patch literal 105186 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 z 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 literal 0 HcmV?d00001 diff --git a/core/test/net/sf/openrocket/file/rasaero/export/02.Two-stage.ork b/core/test/net/sf/openrocket/file/rasaero/export/02.Two-stage.ork new file mode 100644 index 0000000000000000000000000000000000000000..f0a8a101db1aca18fb721005306850ddafd3cff0 GIT binary patch literal 149443 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�co)TmJEYl*>o~Ptg-_V4ouR< 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

ZrxQGh)!EmqAcm>BmJU)yrS_!R!@dq(mh#LNU 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 literal 0 HcmV?d00001 diff --git a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java index 3736c6dad..00ba5b0e2 100644 --- a/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java +++ b/core/test/net/sf/openrocket/file/rasaero/export/RASAeroSaverTest.java @@ -1,14 +1,239 @@ package net.sf.openrocket.file.rasaero.export; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.util.Modules; +import net.sf.openrocket.ServicesForTesting; +import net.sf.openrocket.database.ComponentPresetDao; +import net.sf.openrocket.database.ComponentPresetDatabase; +import net.sf.openrocket.database.motor.MotorDatabase; +import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.OpenRocketDocumentFactory; +import net.sf.openrocket.file.DatabaseMotorFinder; +import net.sf.openrocket.file.DocumentLoadingContext; +import net.sf.openrocket.file.GeneralRocketLoader; +import net.sf.openrocket.file.RocketLoadException; +import net.sf.openrocket.file.rasaero.importt.RASAeroLoader; +import net.sf.openrocket.l10n.DebugTranslator; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.ErrorSet; +import net.sf.openrocket.logging.WarningSet; +import net.sf.openrocket.plugin.PluginModule; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + public class RASAeroSaverTest { // TODO: export a complex design // TODO: check recovery // TODO: check sims (including weights and CG) + @BeforeClass + public static void setup() { + Module applicationModule = new ServicesForTesting(); + + Module pluginModule = new PluginModule(); + + Module debugTranslator = new AbstractModule() { + @Override + protected void configure() { + bind(Translator.class).toInstance(new DebugTranslator(null)); + } + }; + + Module dbOverrides = new AbstractModule() { + @Override + protected void configure() { + bind(ComponentPresetDao.class).toProvider(new EmptyComponentDbProvider()); + bind(MotorDatabase.class).toProvider(new MotorDbProvider()); + } + }; + + Injector injector = Guice.createInjector(Modules.override(applicationModule).with(debugTranslator), pluginModule, dbOverrides); + Application.setInjector(injector); + } + + @Test - public void dummy() { - // We need at least one runnable test method + public void testSingleStage() { + OpenRocketDocument originalDocument = loadRocket("01.One-stage.ork"); + try { + // Convert to RASAero XML + WarningSet warnings = new WarningSet(); + ErrorSet errors = new ErrorSet(); + String result = new RASAeroSaver().marshalToRASAero(originalDocument, warnings, errors); + + assertEquals(" incorrect amount of RASAero export warnings", 3, warnings.size()); + assertEquals(" incorrect amount of RASAero export errors", 0, errors.size()); + + // Write to .CDX1 file + Path output = Files.createTempFile("01.One-stage", ".CDX1"); + Files.write(output, result.getBytes(StandardCharsets.UTF_8)); + + // Read the file + RASAeroLoader loader = new RASAeroLoader(); + InputStream stream = new FileInputStream(output.toFile()); + Assert.assertNotNull("Could not open 01.One-stage.CDX1", stream); + OpenRocketDocument importedDocument = OpenRocketDocumentFactory.createEmptyRocket(); + DocumentLoadingContext context = new DocumentLoadingContext(); + context.setOpenRocketDocument(importedDocument); + context.setMotorFinder(new DatabaseMotorFinder()); + loader.loadFromStream(context, new BufferedInputStream(stream), null); + Rocket importedRocket = importedDocument.getRocket(); + + // Test children counts + List originalChildren = originalDocument.getRocket().getAllChildren(); + List importedChildren = importedRocket.getAllChildren(); + assertEquals(" Number of total children doesn't match", + originalChildren.size(), importedChildren.size()); + + // TODO: check all components + } catch (IllegalStateException ise) { + fail(ise.getMessage()); + } catch (RocketLoadException | IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testTwoStage() { + OpenRocketDocument originalDocument = loadRocket("02.Two-stage.ork"); + try { + // Convert to RASAero XML + WarningSet warnings = new WarningSet(); + ErrorSet errors = new ErrorSet(); + String result = new RASAeroSaver().marshalToRASAero(originalDocument, warnings, errors); + + assertEquals(" incorrect amount of RASAero export warnings", 2, warnings.size()); + assertEquals(" incorrect amount of RASAero export errors", 0, errors.size()); + + // Write to .CDX1 file + Path output = Files.createTempFile("02.Two-stage", ".CDX1"); + Files.write(output, result.getBytes(StandardCharsets.UTF_8)); + + // Read the file + RASAeroLoader loader = new RASAeroLoader(); + InputStream stream = new FileInputStream(output.toFile()); + Assert.assertNotNull("Could not open 02.Two-stage.CDX1", stream); + OpenRocketDocument importedDocument = OpenRocketDocumentFactory.createEmptyRocket(); + DocumentLoadingContext context = new DocumentLoadingContext(); + context.setOpenRocketDocument(importedDocument); + context.setMotorFinder(new DatabaseMotorFinder()); + loader.loadFromStream(context, new BufferedInputStream(stream), null); + Rocket importedRocket = importedDocument.getRocket(); + + // Test children counts + List importedChildren = importedRocket.getAllChildren(); + assertEquals(" Number of total children doesn't match", + 18, importedChildren.size()); + } catch (IllegalStateException ise) { + fail(ise.getMessage()); + } catch (RocketLoadException | IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testThreeStage() { + OpenRocketDocument originalDocument = loadRocket("03.Three-stage.ork"); + try { + // Convert to RASAero XML + WarningSet warnings = new WarningSet(); + ErrorSet errors = new ErrorSet(); + String result = new RASAeroSaver().marshalToRASAero(originalDocument, warnings, errors); + + assertEquals(" incorrect amount of RASAero export warnings", 2, warnings.size()); + assertEquals(" incorrect amount of RASAero export errors", 0, errors.size()); + + // Write to .CDX1 file + Path output = Files.createTempFile("03.Three-stage", ".CDX1"); + Files.write(output, result.getBytes(StandardCharsets.UTF_8)); + + // Read the file + RASAeroLoader loader = new RASAeroLoader(); + InputStream stream = new FileInputStream(output.toFile()); + Assert.assertNotNull("Could not open 03.Three-stage.CDX1", stream); + OpenRocketDocument importedDocument = OpenRocketDocumentFactory.createEmptyRocket(); + DocumentLoadingContext context = new DocumentLoadingContext(); + context.setOpenRocketDocument(importedDocument); + context.setMotorFinder(new DatabaseMotorFinder()); + loader.loadFromStream(context, new BufferedInputStream(stream), null); + Rocket importedRocket = importedDocument.getRocket(); + + // Test children counts + List importedChildren = importedRocket.getAllChildren(); + assertEquals(" Number of total children doesn't match", + 21, importedChildren.size()); + } catch (IllegalStateException ise) { + fail(ise.getMessage()); + } catch (RocketLoadException | IOException e) { + throw new RuntimeException(e); + } + } + + private OpenRocketDocument loadRocket(String fileName) { + GeneralRocketLoader loader = new GeneralRocketLoader(new File(fileName)); + InputStream is = this.getClass().getResourceAsStream(fileName); + String failMsg = String.format("Problem in unit test, cannot find %s", fileName); + assertNotNull(failMsg, is); + + OpenRocketDocument rocketDoc = null; + try { + rocketDoc = loader.load(is, fileName); + } catch (RocketLoadException e) { + fail("RocketLoadException while loading file " + fileName + " : " + e.getMessage()); + } + + try { + is.close(); + } catch (IOException e) { + fail("Unable to close input stream for file " + fileName + ": " + e.getMessage()); + } + + return rocketDoc; + } + + private static class EmptyComponentDbProvider implements Provider { + + final ComponentPresetDao db = new ComponentPresetDatabase(); + + @Override + public ComponentPresetDao get() { + return db; + } + } + + private static class MotorDbProvider implements Provider { + + final ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(); + + public MotorDbProvider() { + } + + @Override + public ThrustCurveMotorSetDatabase get() { + return db; + } } } From 09a586374e264b975ea30bd2c943d36d58a61969 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 13 Apr 2023 10:09:18 +0200 Subject: [PATCH 66/76] Update component database --- swing/resources-src/datafiles/components-dbcook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swing/resources-src/datafiles/components-dbcook b/swing/resources-src/datafiles/components-dbcook index 827509c8e..9da5f4e25 160000 --- a/swing/resources-src/datafiles/components-dbcook +++ b/swing/resources-src/datafiles/components-dbcook @@ -1 +1 @@ -Subproject commit 827509c8eaa27e456095ffe57009c53aa6ee4f28 +Subproject commit 9da5f4e25f57e8ec476ea38cd91c78edff06003f From 3f86a868ee326a27fdf8d6db8b2058cb1c4bf5f4 Mon Sep 17 00:00:00 2001 From: May Date: Mon, 24 Apr 2023 01:03:30 -0400 Subject: [PATCH 67/76] [#2161] Add setForeground and setBackground functions to DescriptionArea --- .../sf/openrocket/gui/components/DescriptionArea.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java index b60d4f6ad..39ca75da0 100644 --- a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java +++ b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java @@ -202,5 +202,15 @@ public class DescriptionArea extends JScrollPane { editorPane.setFont(font); } } + + public void setBackground(Color color) { + if (editorPane == null) return; + editorPane.setBackground(color); + } + + public void setForeground(Color color) { + if (editorPane == null) return; + editorPane.setForeground(color); + } } From 657f8b761ae4e583eaed8e1d0ba90137902be7a5 Mon Sep 17 00:00:00 2001 From: May Date: Mon, 24 Apr 2023 01:05:52 -0400 Subject: [PATCH 68/76] [#2161] Improve "data will be plotted in time order" warning --- .../gui/simulation/SimulationPlotPanel.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java index c28cc606c..bec59b70c 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.simulation; +import java.awt.Color; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -105,7 +106,8 @@ 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,13 @@ 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.RED); + + simPlotPanelDesc.setViewportBorder(BorderFactory.createEmptyBorder()); + this.add(simPlotPanelDesc, "width 1px, growx 1, wrap unrel"); + From a40dc83aeb5e7b908a6d9ca6ece8fb7844a07692 Mon Sep 17 00:00:00 2001 From: May Date: Mon, 24 Apr 2023 21:59:07 -0400 Subject: [PATCH 69/76] Use openrocket Color instead of awt Color --- .../sf/openrocket/gui/simulation/SimulationPlotPanel.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java index bec59b70c..d926118a9 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java @@ -1,6 +1,5 @@ package net.sf.openrocket.gui.simulation; -import java.awt.Color; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -41,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; @@ -205,8 +205,7 @@ public class SimulationPlotPanel extends JPanel { //// The data will be plotted in time order even if the X axis type is not time. simPlotPanelDesc = new DescriptionArea("", 2, -2f, false); simPlotPanelDesc.setVisible(false); - simPlotPanelDesc.setForeground(Color.RED); - + simPlotPanelDesc.setForeground(Color.DARK_RED.toAWTColor()); simPlotPanelDesc.setViewportBorder(BorderFactory.createEmptyBorder()); this.add(simPlotPanelDesc, "width 1px, growx 1, wrap unrel"); From 508160f88bfab9b6f88bc64da78b67c4a6bd8107 Mon Sep 17 00:00:00 2001 From: May Date: Mon, 24 Apr 2023 22:16:47 -0400 Subject: [PATCH 70/76] Add warning message to plot display --- .../sf/openrocket/gui/plot/SimulationPlotDialog.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java index d9c1724ea..0d23aeacb 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java @@ -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; @@ -71,6 +73,16 @@ public class SimulationPlotDialog extends JDialog { //// Description text 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) { + // TODO: LOW: This translation message doesn't use the proper tense (simple future when it would be present) + // There is currently no translation message representing this dialog. + // Such a message should be added, and this code should be updated to use it. + JLabel msg = new StyledLabel(trans.get("simplotpanel.Desc"), -2); + msg.setForeground(Color.DARK_RED.toAWTColor()); + panel.add(msg, "wrap"); + } //// Show data points final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints")); From 3ab2c13b4c5d71340842fe061fd57f1ebcc07f43 Mon Sep 17 00:00:00 2001 From: May Date: Tue, 25 Apr 2023 14:18:33 -0400 Subject: [PATCH 71/76] Add translation message for plot warning --- core/resources/l10n/messages.properties | 2 ++ .../src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 2e3bbdc1c..7c81230ca 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -767,6 +767,8 @@ simplotpanel.but.NewYaxisplottype = New Y axis plot type simplotpanel.lbl.Axis = Axis: simplotpanel.but.ttip.Deletethisplot = Delete this plot simplotpanel.Desc = The data will be plotted in time order even if the X axis type is not time. +! Fix this name +simplotpanel.Warning = The data is plotted in time order even though the X axis type is not time. simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed. simplotpanel.OptionPane.lbl2 = Cannot add plot simplotpanel.AUTO_NAME = Auto diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java index 0d23aeacb..b6f204770 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java @@ -76,10 +76,7 @@ public class SimulationPlotDialog extends JDialog { // Add warning if X axis type is not time if (config.getDomainAxisType() != FlightDataType.TYPE_TIME) { - // TODO: LOW: This translation message doesn't use the proper tense (simple future when it would be present) - // There is currently no translation message representing this dialog. - // Such a message should be added, and this code should be updated to use it. - JLabel msg = new StyledLabel(trans.get("simplotpanel.Desc"), -2); + JLabel msg = new StyledLabel(trans.get("simplotpanel.Warning"), -2); msg.setForeground(Color.DARK_RED.toAWTColor()); panel.add(msg, "wrap"); } From 8d1ce46d095de9da197298fa9fc3022bdf0e453c Mon Sep 17 00:00:00 2001 From: May Date: Tue, 25 Apr 2023 14:19:41 -0400 Subject: [PATCH 72/76] Fix problem with setForeground and setBackground --- .../openrocket/gui/components/DescriptionArea.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java index 39ca75da0..2c666eff2 100644 --- a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java +++ b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java @@ -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)); } @@ -206,11 +210,19 @@ 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); } } From ff47b8117a04cb7ca46c7419a89cad3bd9940e1a Mon Sep 17 00:00:00 2001 From: May Date: Wed, 26 Apr 2023 14:57:05 -0400 Subject: [PATCH 73/76] Change plot warning message name --- core/resources/l10n/messages.properties | 3 +-- swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 7c81230ca..615319afe 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -767,8 +767,6 @@ simplotpanel.but.NewYaxisplottype = New Y axis plot type simplotpanel.lbl.Axis = Axis: simplotpanel.but.ttip.Deletethisplot = Delete this plot simplotpanel.Desc = The data will be plotted in time order even if the X axis type is not time. -! Fix this name -simplotpanel.Warning = The data is plotted in time order even though the X axis type is not time. simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed. simplotpanel.OptionPane.lbl2 = Cannot add plot simplotpanel.AUTO_NAME = Auto @@ -1506,6 +1504,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 diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java index b6f204770..db19ab496 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java @@ -76,7 +76,7 @@ public class SimulationPlotDialog extends JDialog { // Add warning if X axis type is not time if (config.getDomainAxisType() != FlightDataType.TYPE_TIME) { - JLabel msg = new StyledLabel(trans.get("simplotpanel.Warning"), -2); + JLabel msg = new StyledLabel(trans.get("PlotDialog.lbl.timeSeriesWarning"), -2); msg.setForeground(Color.DARK_RED.toAWTColor()); panel.add(msg, "wrap"); } From 3ffdf5fb349d63e525ea1a40fac264f4d93775cc Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sat, 6 May 2023 16:01:02 +0200 Subject: [PATCH 74/76] Add font info --- core/resources-src/pix/splashscreen.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/resources-src/pix/splashscreen.txt b/core/resources-src/pix/splashscreen.txt index cb22901ce..7c9fa3cdd 100644 --- a/core/resources-src/pix/splashscreen.txt +++ b/core/resources-src/pix/splashscreen.txt @@ -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: From 8aac8594d3d5ed7f38a26cd4cfe507630ea9ae86 Mon Sep 17 00:00:00 2001 From: thzero Date: Sat, 6 May 2023 11:31:24 -0500 Subject: [PATCH 75/76] updates --- .../java/net/sf/openrocket/logging/Error.java | 2 +- .../net/sf/openrocket/logging/Warning.java | 2 +- .../file/rasaero/CustomBooleanAdapter.java | 19 - .../file/rasaero/CustomDoubleAdapter.java | 22 - .../file/rasaero/RASAeroCommonConstants.java | 517 ------------------ .../file/rasaero/RASAeroMotorsLoader.java | 104 ---- .../file/rasaero/export/BasePartDTO.java | 148 ----- .../file/rasaero/export/BoattailDTO.java | 28 - .../file/rasaero/export/BodyTubeDTO.java | 192 ------- .../rasaero/export/BodyTubeDTOAdapter.java | 101 ---- .../file/rasaero/export/BoosterDTO.java | 369 ------------- .../file/rasaero/export/FinDTO.java | 168 ------ .../file/rasaero/export/LaunchSiteDTO.java | 121 ---- .../file/rasaero/export/NoseConeDTO.java | 84 --- .../rasaero/export/RASAeroDocumentDTO.java | 83 --- .../file/rasaero/export/RASAeroSaver.java | 138 ----- .../file/rasaero/export/RecoveryDTO.java | 245 --------- .../file/rasaero/export/RocketDesignDTO.java | 229 -------- .../file/rasaero/export/SimulationDTO.java | 496 ----------------- .../rasaero/export/SimulationListDTO.java | 102 ---- .../file/rasaero/export/TransitionDTO.java | 84 --- core/src/net/sf/openrocket/logging/Error.java | 59 -- .../net/sf/openrocket/logging/ErrorSet.java | 32 -- .../net/sf/openrocket/logging/Message.java | 43 -- .../net/sf/openrocket/logging/MessageSet.java | 123 ----- .../net/sf/openrocket/logging/Warning.java | 392 ------------- .../net/sf/openrocket/logging/WarningSet.java | 48 -- .../gui/dialogs/ErrorWarningDialog.java | 95 ---- .../gui/util/BetterListCellRenderer.java | 36 -- 29 files changed, 2 insertions(+), 4080 deletions(-) delete mode 100644 core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java delete mode 100644 core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java delete mode 100644 core/src/net/sf/openrocket/logging/Error.java delete mode 100644 core/src/net/sf/openrocket/logging/ErrorSet.java delete mode 100644 core/src/net/sf/openrocket/logging/Message.java delete mode 100644 core/src/net/sf/openrocket/logging/MessageSet.java delete mode 100644 core/src/net/sf/openrocket/logging/Warning.java delete mode 100644 core/src/net/sf/openrocket/logging/WarningSet.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java delete mode 100644 swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java diff --git a/core/src/main/java/net/sf/openrocket/logging/Error.java b/core/src/main/java/net/sf/openrocket/logging/Error.java index 69ba76dd6..db94d39d2 100644 --- a/core/src/main/java/net/sf/openrocket/logging/Error.java +++ b/core/src/main/java/net/sf/openrocket/logging/Error.java @@ -13,7 +13,7 @@ public abstract class Error extends Message { * @return an Error with the specific text. */ public static Error fromString(String text) { - return new Error.Other(text); + return new Other(text); } diff --git a/core/src/main/java/net/sf/openrocket/logging/Warning.java b/core/src/main/java/net/sf/openrocket/logging/Warning.java index 9d1bc0421..35654250c 100644 --- a/core/src/main/java/net/sf/openrocket/logging/Warning.java +++ b/core/src/main/java/net/sf/openrocket/logging/Warning.java @@ -18,7 +18,7 @@ public abstract class Warning extends Message { * @return a Warning with the specific text. */ public static Warning fromString(String text) { - return new Warning.Other(text); + return new Other(text); } diff --git a/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java b/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java deleted file mode 100644 index 336f326ed..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/CustomBooleanAdapter.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.sf.openrocket.file.rasaero; - -import javax.xml.bind.annotation.adapters.XmlAdapter; - -public class CustomBooleanAdapter extends XmlAdapter { - - @Override - public Boolean unmarshal(String s) throws Exception { - return "true".equalsIgnoreCase(s); - } - - @Override - public String marshal(Boolean b) throws Exception { - if (b) { - return "True"; - } - return "False"; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java b/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java deleted file mode 100644 index 77b600728..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/CustomDoubleAdapter.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.sf.openrocket.file.rasaero; - -import javax.xml.bind.annotation.adapters.XmlAdapter; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; - -public class CustomDoubleAdapter extends XmlAdapter { - @Override - public Double unmarshal(String s) throws Exception { - return Double.parseDouble(s); - } - - @Override - public String marshal(Double aDouble) throws Exception { - if (aDouble == null) { - return null; - } - DecimalFormat df = new DecimalFormat("#.####", new DecimalFormatSymbols(Locale.US)); // RASAero has 4 decimal precision - return df.format(aDouble); - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java deleted file mode 100644 index 8c71fd2bf..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroCommonConstants.java +++ /dev/null @@ -1,517 +0,0 @@ -package net.sf.openrocket.file.rasaero; - -import net.sf.openrocket.file.motor.AbstractMotorLoader; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.motor.Manufacturer; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.MotorConfiguration; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; -import net.sf.openrocket.rocketcomponent.ExternalComponent; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.util.Color; -import net.sf.openrocket.util.MathUtil; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * List of constants used in RASAero files + helper functions to read parameters from it. - * - * @author Sibo Van Gool - */ -public class RASAeroCommonConstants { - // File settings - public static final String FILE_EXTENSION = "CDX1"; - - // General settings - public static final String RASAERO_DOCUMENT = "RASAeroDocument"; - public static final String FILE_VERSION = "FileVersion"; - public static final String ROCKET_DESIGN = "RocketDesign"; - - // RASAeroDocument settings - public static final String MACH_ALT = "MachAlt"; - - // Base part settings - public static final String PART_TYPE = "PartType"; - public static final String LENGTH = "Length"; - public static final String DIAMETER = "Diameter"; - public static final String LOCATION = "Location"; - public static final String COLOR = "Color"; - public static final String SURFACE_FINISH = "Surface"; - public static final String COMMENTS = "Comments"; - - // Components - public static final String NOSE_CONE = "NoseCone"; - public static final String BODY_TUBE = "BodyTube"; - public static final String TRANSITION = "Transition"; - public static final String FIN = "Fin"; - public static final String BOOSTER = "Booster"; - public static final String FIN_CAN = "FinCan"; - public static final String BOATTAIL = "BoatTail"; - - // Body tube settings - public static final String OVERHANG = "Overhang"; - - // Nose cone settings - public static final String SHAPE = "Shape"; - public static final String POWER_LAW = "PowerLaw"; - public static final String BLUNT_RADIUS = "BluntRadius"; - private static final Map RASAeroNoseConeShapeMap = new HashMap<>(); - - //// Nose cone shapes - private static final String SHAPE_CONICAL = "Conical"; - private static final String SHAPE_TANGENT_OGIVE = "Tangent Ogive"; - private static final String SHAPE_VON_KARMAN_OGIVE = "Von Karman Ogive"; - private static final String SHAPE_POWER_LAW = "Power Law"; - private static final String SHAPE_LVHAACK = "LV-Haack"; - private static final String SHAPE_PARABOLIC = "Parabolic"; - private static final String SHAPE_ELLIPTICAL = "Elliptical"; - - // Transition settings - public static final String REAR_DIAMETER = "RearDiameter"; - - // Fin settings - public static final String FIN_COUNT = "Count"; - public static final String FIN_CHORD = "Chord"; - public static final String FIN_SPAN = "Span"; - public static final String FIN_SWEEP_DISTANCE = "SweepDistance"; - public static final String FIN_TIP_CHORD = "TipChord"; - public static final String FIN_THICKNESS = "Thickness"; - public static final String FIN_LE_RADIUS = "LERadius"; - public static final String FIN_AIRFOIL_SECTION = "AirfoilSection"; - public static final String FIN_FX1 = "FX1"; - public static final String FIN_FX3 = "FX3"; - public static final String AIRFOIL_SECTION = "AirfoilSection"; - //// LERadius, FX1 and FX3 not used - public static final String CROSS_SECTION_SQUARE = "Square"; - public static final String CROSS_SECTION_ROUNDED = "Rounded"; - public static final String CROSS_SECTION_SUBSONIC_NACA = "Subsonic NACA"; - - // Launch lug settings - public static final String LAUNCH_LUG_DIAMETER = "LaunchLugDiameter"; - public static final String LAUNCH_LUG_LENGTH = "LaunchLugLength"; - - // Rail guide settings - public static final String RAIL_GUIDE_DIAMETER = "RailGuideDiameter"; - public static final String RAIL_GUIDE_HEIGHT = "RailGuideHeight"; - - // Launch shoe settings - public static final String LAUNCH_SHOE_AREA = "LaunchShoeArea"; - - // Fin can settings - public static final String INSIDE_DIAMETER = "InsideDiameter"; - public static final String SHOULDER_LENGTH = "ShoulderLength"; - - // Surface finishes - public static final String FINISH_SMOOTH = "Smooth (Zero Roughness)"; - public static final String FINISH_POLISHED = "Polished"; - public static final String FINISH_SHEET_METAL = "Sheet Metal"; - public static final String FINISH_SMOOTH_PAINT = "Smooth Paint"; - public static final String FINISH_CAMOUFLAGE = "Camouflage Paint"; - public static final String FINISH_ROUGH_CAMOUFLAGE = "Rough Camouflage Paint"; - public static final String FINISH_GALVANIZED = "Galvanized Metal"; - public static final String FINISH_CAST_IRON = "Cast Iron (Very Rough)"; - - // Booster settings - public static final String BOATTAIL_LENGTH = "BoattailLength"; - public static final String BOATTAIL_REAR_DIAMETER = "BoattailRearDiameter"; - public static final String BOATTAIL_OFFSET = "BoattailOffset"; - public static final String NOZZLE_EXIT_DIAMETER = "NozzleExitDiameter"; - - // RocketDesign settings - public static final String CD = "CD"; - public static final String MODIFIED_BARROWMAN = "ModifiedBarrowman"; - public static final String TURBULENCE = "Turbulence"; - public static final String SUSTAINER_NOZZLE = "SustainerNozzle"; - public static final String BOOSTER1_NOZZLE = "Booster1Nozzle"; - public static final String BOOSTER2_NOZZLE = "Booster2Nozzle"; - public static final String USE_BOOSTER1 = "UseBooster1"; - public static final String USE_BOOSTER2 = "UseBooster2"; - - // Launch site settings - public static final String LAUNCH_SITE = "LaunchSite"; - public static final String LAUNCH_ALTITUDE = "Altitude"; - public static final String LAUNCH_PRESSURE = "Pressure"; - public static final String LAUNCH_ROD_ANGLE = "RodAngle"; - public static final String LAUNCH_ROD_LENGTH = "RodLength"; - public static final String LAUNCH_TEMPERATURE = "Temperature"; - public static final String LAUNCH_WIND_SPEED = "WindSpeed"; - - // Recovery settings - public static final String RECOVERY = "Recovery"; - public static final String RECOVERY_ALTITUDE = "Altitude"; - public static final String RECOVERY_DEVICE_TYPE = "DeviceType"; - public static final String RECOVERY_EVENT = "Event"; - public static final String RECOVERY_SIZE = "Size"; - public static final String RECOVERY_EVENT_TYPE = "EventType"; - public static final String RECOVERY_CD = "CD"; - - // Deployment settings - public static final String DEPLOYMENT_NONE = "None"; - public static final String DEPLOYMENT_APOGEE = "Apogee"; - public static final String DEPLOYMENT_ALTITUDE = "Altitude"; - - // Simulation settings - public static final String SIMULATION_LIST = "SimulationList"; - public static final String SIMULATION = "Simulation"; - public static final String SUSTAINER_ENGINE = "SustainerEngine"; - public static final String SUSTAINER_LAUNCH_WT = "SustainerLaunchWt"; - public static final String SUSTAINER_NOZZLE_DIAMETER = "SustainerNozzleDiameter"; - public static final String SUSTAINER_CG = "SustainerCG"; - public static final String SUSTAINER_IGNITION_DELAY = "SustainerIgnitionDelay"; - public static final String BOOSTER1_ENGINE = "Booster1Engine"; - public static final String BOOSTER1_SEPARATION_DELAY = "Booster1SeparationDelay"; // Delay after booster burnout to separate - public static final String BOOSTER1_IGNITION_DELAY = "Booster1IgnitionDelay"; - public static final String BOOSTER1_LAUNCH_WT = "Booster1LaunchWt"; - public static final String BOOSTER1_NOZZLE_DIAMETER = "Booster1NozzleDiameter"; - public static final String BOOSTER1_CG = "Booster1CG"; - public static final String INCLUDE_BOOSTER1 = "IncludeBooster1"; - public static final String BOOSTER2_ENGINE = "Booster2Engine"; - public static final String BOOSTER2_SEPARATION_DELAY = "Booster2Delay"; // Delay after booster burnout to separate - public static final String BOOSTER2_LAUNCH_WT = "Booster2LaunchWt"; - public static final String BOOSTER2_NOZZLE_DIAMETER = "Booster2NozzleDiameter"; - public static final String BOOSTER2_CG = "Booster2CG"; - public static final String INCLUDE_BOOSTER2 = "IncludeBooster2"; - public static final String FLIGHT_TIME = "FlightTime"; - public static final String TIME_TO_APOGEE = "TimetoApogee"; - public static final String MAX_ALTITUDE = "MaxAltitude"; - public static final String MAX_VELOCITY = "MaxVelocity"; - public static final String OPTIMUM_WT = "OptimumWt"; - public static final String OPTIMUM_MAX_ALT = "OptimumMaxAlt"; - - - /** - * Length conversion from OpenRocket units to RASAero units. RASAero is in inches, OpenRocket in meters. - */ - public static final double OPENROCKET_TO_RASAERO_LENGTH = 39.37; - /** - * Altitude conversion from OpenRocket units to RASAero units. RASAero is in feet, OpenRocket in meters. - */ - public static final double OPENROCKET_TO_RASAERO_ALTITUDE = 3.28084; - /** - * Speed conversion from OpenRocket units to RASAero units. RASAero is in mph, OpenRocket in m/s. - */ - public static final double OPENROCKET_TO_RASAERO_SPEED = 2.23694; - /** - * Pressure conversion from OpenRocket units to RASAero units. RASAero is in in-hg, OpenRocket in Pa. - */ - public static final double OPENROCKET_TO_RASAERO_PRESSURE = 0.000295301; - /** - * Angle conversion from OpenRocket units to RASAero units. RASAero is in degrees, OpenRocket in rad. - */ - public static final double OPENROCKET_TO_RASAERO_ANGLE = 180 / Math.PI; - /** - * Weight conversion from OpenRocket units to RASAero units. RASAero is in pounds (lb), OpenRocket in kilograms (kg). - */ - public static final double OPENROCKET_TO_RASAERO_WEIGHT = 2.20462262; - /** - * Temperature conversion from OpenRocket units to RASAero units. RASAero is in Fahrenheit, OpenRocket in Kelvin. - */ - public static final double RASAERO_TO_OPENROCKET_TEMPERATURE(Double input) { - return (input + 459.67) * 5.0 / 9.0; - } - public static final double OPENROCKET_TO_RASAERO_TEMPERATURE(Double input) { - return input * 9.0 / 5.0 - 459.67; - } - - static { - RASAeroNoseConeShapeMap.put(SHAPE_CONICAL, Transition.Shape.CONICAL); - RASAeroNoseConeShapeMap.put(SHAPE_TANGENT_OGIVE, Transition.Shape.OGIVE); // = Ogive with shape parameter = 1 - RASAeroNoseConeShapeMap.put(SHAPE_VON_KARMAN_OGIVE, Transition.Shape.HAACK); // = Haack series with shape parameter = 0 - RASAeroNoseConeShapeMap.put(SHAPE_POWER_LAW, Transition.Shape.POWER); - RASAeroNoseConeShapeMap.put(SHAPE_LVHAACK, Transition.Shape.HAACK); // = Haack series with shape parameter = 1/3 - RASAeroNoseConeShapeMap.put(SHAPE_PARABOLIC, Transition.Shape.POWER); // = Power law with shape parameter = 1/2 - RASAeroNoseConeShapeMap.put(SHAPE_ELLIPTICAL, Transition.Shape.ELLIPSOID); - } - - private static final Logger log = LoggerFactory.getLogger(RASAeroCommonConstants.class); - - /** - * Returns the OpenRocket nose cone shape from the RASAero shape string. - * @param shape The RASAero shape string. - * @return The OpenRocket nose cone shape. - */ - public static Transition.Shape RASAERO_TO_OPENROCKET_SHAPE(String shape) { - return RASAeroNoseConeShapeMap.get(shape); - } - - /** - * Returns the OpenRocket nose cone shape from the RASAero shape string. - * @param shape The RASAero shape string. - * @return The OpenRocket nose cone shape object. - */ - public static NoseConeShapeSettings OPENROCKET_TO_RASAERO_SHAPE(Transition.Shape shape, double shapeParameter) - throws RASAeroExportException { - if (shape.equals(Transition.Shape.CONICAL)) { - return new NoseConeShapeSettings(SHAPE_CONICAL); - } else if (shape.equals(Transition.Shape.OGIVE) && MathUtil.equals(shapeParameter, 1)) { - return new NoseConeShapeSettings(SHAPE_TANGENT_OGIVE); - } else if (shape.equals(Transition.Shape.HAACK) && MathUtil.equals(shapeParameter, 0)) { - return new NoseConeShapeSettings(SHAPE_VON_KARMAN_OGIVE); - } else if (shape.equals(Transition.Shape.POWER) && MathUtil.equals(shapeParameter, 0.5, 0.01)) { - return new NoseConeShapeSettings(SHAPE_PARABOLIC); - } else if (shape.equals(Transition.Shape.POWER)) { - return new NoseConeShapeSettings(SHAPE_POWER_LAW, shapeParameter); - } else if (shape.equals(Transition.Shape.HAACK) && MathUtil.equals(shapeParameter, 0.33, 0.01)) { - return new NoseConeShapeSettings(SHAPE_LVHAACK); - } else if (shape.equals(Transition.Shape.ELLIPSOID)) { - return new NoseConeShapeSettings(SHAPE_ELLIPTICAL); - } - - // Special cases - else if (shape.equals(Transition.Shape.OGIVE)) { - throw new RASAeroExportException( - String.format("RASAero only supports Ogive nose cones with shape parameter 1, not %.2f", shapeParameter)); - } else if (shape.equals(Transition.Shape.HAACK)) { - throw new RASAeroExportException( - String.format("RASAero only supports Haack nose cones with shape parameter 0 or 0.33, not %.2f", shapeParameter)); - } else if (shape.equals(Transition.Shape.PARABOLIC)) { - throw new RASAeroExportException("RASAero does not support Parabolic nose cones"); - } - - throw new RASAeroExportException( - String.format("Invalid shape and shape parameter combination: %s, %.2f", shape.getName(), shapeParameter)); - } - - /** - * RASAero has a slightly different way of specifying shapes compared to OpenRocket. For instance - * RASAero has an "LV-Haack" shape, which is the same as the OpenRocket "Haack" shape with a shape - * parameter of 1/3. - * This method returns the shape parameter for the RASAero shape to get the correct OpenRocket nose cone shape. - * @param shape The RASAero shape string. - * @return The shape parameter for the OpenRocket nose cone. - */ - public static double RASAERO_TO_OPENROCKET_SHAPE_PARAMETER(String shape) { - if (SHAPE_CONICAL.equals(shape)) { - return 0.0; // Not really needed, but just to be explicit - } else if (SHAPE_TANGENT_OGIVE.equals(shape)) { - return 1.0; - } else if (SHAPE_VON_KARMAN_OGIVE.equals(shape)) { - return 0.0; - } else if (SHAPE_POWER_LAW.equals(shape)) { - return 0.0; // Not really needed, but just to be explicit (will be overwritten later) - } else if (SHAPE_LVHAACK.equals(shape)) { - return 0.33; - } else if (SHAPE_PARABOLIC.equals(shape)) { - return 0.5; - } else if (SHAPE_ELLIPTICAL.equals(shape)) { - return 0.0; // Not really needed, but just to be explicit - } else { - return 0.0; - } - } - - public static FinSet.CrossSection RASAERO_TO_OPENROCKET_FIN_CROSSSECTION(String crossSection, WarningSet warnings) { - if (CROSS_SECTION_SQUARE.equals(crossSection)) { - return FinSet.CrossSection.SQUARE; - } else if (CROSS_SECTION_ROUNDED.equals(crossSection)) { - return FinSet.CrossSection.ROUNDED; - } else if (CROSS_SECTION_SUBSONIC_NACA.equals(crossSection)) { - return FinSet.CrossSection.AIRFOIL; - } else { - String msg = "Unknown fin cross section: " + crossSection + ", defaulting to Airfoil."; - warnings.add(msg); - log.debug(msg); - return FinSet.CrossSection.AIRFOIL; - } - } - - public static String OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(FinSet.CrossSection crossSection, WarningSet warnings) { - if (FinSet.CrossSection.SQUARE.equals(crossSection)) { - return CROSS_SECTION_SQUARE; - } else if (FinSet.CrossSection.ROUNDED.equals(crossSection)) { - return CROSS_SECTION_ROUNDED; - } else if (FinSet.CrossSection.AIRFOIL.equals(crossSection)) { - return CROSS_SECTION_SUBSONIC_NACA; - } else { - String msg = "Unknown fin cross section: " + crossSection + "."; - warnings.add(msg); - log.warn(msg); - return null; - } - } - - public static ExternalComponent.Finish RASAERO_TO_OPENROCKET_SURFACE(String surfaceFinish, WarningSet warnings) { - // NOTE: the RASAero surface finishes are not really the same as the OpenRocket surface finishes. There are some - // approximations here. - if (FINISH_SMOOTH.equals(surfaceFinish)) { - return ExternalComponent.Finish.MIRROR; - } else if (FINISH_POLISHED.equals(surfaceFinish)) { - return ExternalComponent.Finish.FINISHPOLISHED; - } else if (FINISH_SHEET_METAL.equals(surfaceFinish)) { - return ExternalComponent.Finish.OPTIMUM; - } else if (FINISH_SMOOTH_PAINT.equals(surfaceFinish)) { - return ExternalComponent.Finish.OPTIMUM; - } else if (FINISH_CAMOUFLAGE.equals(surfaceFinish)) { - return ExternalComponent.Finish.SMOOTH; - } else if (FINISH_ROUGH_CAMOUFLAGE.equals(surfaceFinish)) { - return ExternalComponent.Finish.NORMAL; - } else if (FINISH_GALVANIZED.equals(surfaceFinish)) { - return ExternalComponent.Finish.UNFINISHED; - } else if (FINISH_CAST_IRON.equals(surfaceFinish)) { - return ExternalComponent.Finish.ROUGHUNFINISHED; - } else { - String msg = "Unknown surface finish: " + surfaceFinish + ", defaulting to Regular Paint."; - warnings.add(msg); - log.debug(msg); - return ExternalComponent.Finish.NORMAL; - } - } - - public static String OPENROCKET_TO_RASAERO_SURFACE(ExternalComponent.Finish finish, WarningSet warnings) { - if (finish.equals(ExternalComponent.Finish.MIRROR)) { - return FINISH_SMOOTH; - } else if (finish.equals(ExternalComponent.Finish.FINISHPOLISHED)) { - return FINISH_POLISHED; - } else if (finish.equals(ExternalComponent.Finish.OPTIMUM)) { - return FINISH_SHEET_METAL; - } else if (finish.equals(ExternalComponent.Finish.SMOOTH)) { - return FINISH_CAMOUFLAGE; - } else if (finish.equals(ExternalComponent.Finish.NORMAL)) { - return FINISH_ROUGH_CAMOUFLAGE; - } else if (finish.equals(ExternalComponent.Finish.UNFINISHED)) { - return FINISH_GALVANIZED; - } else if (finish.equals(ExternalComponent.Finish.ROUGHUNFINISHED)) { - return FINISH_CAST_IRON; - } else { - String msg = "Unknown surface finish: " + finish + ", defaulting to Smooth."; - warnings.add(msg); - log.debug(msg); - return FINISH_SMOOTH; - } - } - - /** - * Format an OpenRocket motor as a RASAero motor. - * @param RASAeroMotors list of available RASAero motors - * @param ORMotor OpenRocket motor - * @return a RASAero String representation of a motor - */ - public static String OPENROCKET_TO_RASAERO_MOTOR(List RASAeroMotors, Motor ORMotor, - WarningSet warnings) { - if (!(ORMotor instanceof ThrustCurveMotor)) { - log.debug("RASAero motor not found: not a thrust curve motor"); - return null; - } - - for (ThrustCurveMotor RASAeroMotor : RASAeroMotors) { - String RASAeroDesignation = AbstractMotorLoader.removeDelay(RASAeroMotor.getDesignation()); - if (ORMotor.getDesignation().equals(RASAeroDesignation) && - ((ThrustCurveMotor) ORMotor).getManufacturer().matches(RASAeroMotor.getManufacturer().getDisplayName())) { - String motorName = RASAeroMotor.getDesignation(); - log.debug(String.format("RASAero RASAeroMotor found: %s", motorName)); - return motorName + " (" + OPENROCKET_TO_RASAERO_MANUFACTURER(RASAeroMotor.getManufacturer()) + ")"; - } - } - - String msg = String.format("Could not find RASAero motor for '%s'", ORMotor.getDesignation()); - warnings.add(msg); - log.debug(msg); - return null; - } - - public static String OPENROCKET_TO_RASAERO_MANUFACTURER(Manufacturer manufacturer) { - if (manufacturer.matches("AeroTech")) { - return "AT"; - } else if (manufacturer.matches("Estes")) { - return "ES"; - } else if (manufacturer.matches("Apogee")) { - return "AP"; - } else if (manufacturer.matches("Quest")) { - return "QU"; - } else if (manufacturer.matches("Cesaroni")) { - return "CTI"; - } else if (manufacturer.matches("NoThrust")) { - return "NoThrust"; - } else if (manufacturer.matches("Ellis Mountain")) { - return "EM"; - } else if (manufacturer.matches("Contrail")) { - return "Contrail"; - } else if (manufacturer.matches("Rocketvision")) { - return "RV"; - } else if (manufacturer.matches("Roadrunner Rocketry")) { - return "RR"; - } else if (manufacturer.matches("Sky Ripper Systems")) { - return "SRS"; - } else if (manufacturer.matches("Loki Research")) { - return "LR"; - } else if (manufacturer.matches("Public Missiles, Ltd.")) { - return "PML"; - } else if (manufacturer.matches("Kosdon by AeroTech")) { - return "KBA"; - } else if (manufacturer.matches("Gorilla Rocket Motors")) { - return "GM"; - } else if (manufacturer.matches("RATT Works")) { - return "RTW"; - } else if (manufacturer.matches("HyperTEK")) { - return "HT"; - } else if (manufacturer.matches("Animal Motor Works")) { - return "AMW"; - } else if (manufacturer.matches("Loki")) { - return "CT"; - } else if (manufacturer.matches("AMW ProX")) { - return "AMW/ProX"; - } else if (manufacturer.matches("Loki Research EX")) { - return "LR-EX"; - } else if (manufacturer.matches("Derek Deville DEAP EX")) { - return "DEAP-EX"; - } else if (manufacturer.matches("Historical")) { - return "Hist"; - } - return manufacturer.getSimpleName(); - } - - public static DeploymentConfiguration.DeployEvent RASAERO_TO_OPENROCKET_DEPLOY_EVENT(String deployEvent, WarningSet warnings) { - if (DEPLOYMENT_NONE.equals(deployEvent)) { - return DeploymentConfiguration.DeployEvent.NEVER; - } else if (DEPLOYMENT_APOGEE.equals(deployEvent)) { - return DeploymentConfiguration.DeployEvent.APOGEE; - } else if (DEPLOYMENT_ALTITUDE.equals(deployEvent)) { - return DeploymentConfiguration.DeployEvent.ALTITUDE; - } - warnings.add("Unknown deployment event: " + deployEvent + ", defaulting to apogee."); - return DeploymentConfiguration.DeployEvent.APOGEE; - } - - public static String OPENROCKET_TO_RASAERO_COLOR(Color color) { - if (color != null) { - if (color.equals(Color.BLACK)) { - return "Black"; // Currently the only officially supported color by RASAero - } - } - return "Blue"; // But we can also apply our own color hehe - } - - /** - * Class containing the RASAero nose cone shape and shape parameter settings - */ - public static class NoseConeShapeSettings { - private final String shape; - private final Double shapeParameter; - - public NoseConeShapeSettings(String shape, double shapeParameter) { - this.shape = shape; - this.shapeParameter = shapeParameter; - } - - public NoseConeShapeSettings(String shape) { - this.shape = shape; - this.shapeParameter = null; - } - - public String getShape() { - return shape; - } - - public Double getShapeParameter() { - return shapeParameter; - } - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java b/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java deleted file mode 100644 index 43cc3ac75..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/RASAeroMotorsLoader.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.sf.openrocket.file.rasaero; - -import net.sf.openrocket.file.motor.GeneralMotorLoader; -import net.sf.openrocket.file.motor.RASPMotorLoader; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.database.motor.ThrustCurveMotorSet; -import net.sf.openrocket.file.motor.AbstractMotorLoader; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.startup.Application; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -public abstract class RASAeroMotorsLoader { - private static List allMotors = null; - - /** - * Returns a RASAero motor from the motor string of its RASAero file. - * @param motorString The motor string of the RASAero file, e.g. "1/4A2 (AP)". - * @param warnings The warning set to add import warnings to. - * @return The motor, or null if not found. - */ - public static ThrustCurveMotor getMotorFromRASAero(String motorString, WarningSet warnings) { - if (motorString == null) { - return null; - } - if (allMotors == null) { - loadAllMotors(warnings); - } - /* - RASAero file motor strings are formatted as " ()" - */ - String[] split = motorString.split("\\s{2}"); - if (split.length != 2) { - return null; - } - String motorName = AbstractMotorLoader.removeDelay(split[0]); - String manufacturer = split[1].replaceAll("^\\(|\\)$", ""); // Remove beginning and ending parenthesis - for (ThrustCurveMotor motor : allMotors) { - if (motorName.equals(motor.getDesignation()) && motor.getManufacturer().matches(manufacturer)) { - return motor; - } - } - warnings.add("Could not find motor '" + motorString + "' in the OpenRocket motors database. Please add it manually."); - return null; - } - - /** - * Call this method when you don't need the RASAero motors anymore to free memory. - */ - public static void clearAllMotors() { - if (allMotors != null) { - allMotors.clear(); - allMotors = null; - } - } - - // Not currently used for importing, because it causes some compatibility issues when e.g. wanting to open the RASAero motor - // in the motor selection table (because it is not present there). - // It's probably also better to load OR-native motors. - // But I'll leave this in, in case it's needed in the future. - /** - * Loads all original RASAero motors. - * @param warnings The warning set to add import warnings to. - * @return the loaded motors - * @throws RuntimeException If the RASAero motors file could not be found. - */ - public static List loadAllRASAeroMotors(WarningSet warnings) throws RuntimeException { - List RASAeroMotors = new ArrayList<>(); - - RASPMotorLoader loader = new RASPMotorLoader(); - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - String fileName = "RASAero_Motors.eng"; - InputStream is = classloader.getResourceAsStream("datafiles/thrustcurves/RASAero/" + fileName); - if (is == null) { - throw new RuntimeException("Could not find " + fileName); - } - try { - List motors = loader.load(is, fileName, false); - for (ThrustCurveMotor.Builder builder : motors) { - RASAeroMotors.add(builder.build()); - } - } catch (IOException e) { - warnings.add("Error during motor loading: " + e.getMessage()); - } - - return RASAeroMotors; - } - - /** - * Loads the OpenRocket motors database. - */ - private static void loadAllMotors(WarningSet warnings) { - allMotors = new ArrayList<>(); - List database = Application.getThrustCurveMotorSetDatabase().getMotorSets(); - for (ThrustCurveMotorSet set : database) { - allMotors.addAll(set.getMotors()); - } - //allMotors.addAll(loadAllRASAeroMotors(warnings)); - } - -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java deleted file mode 100644 index 926e4ef90..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/BasePartDTO.java +++ /dev/null @@ -1,148 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.NoseCone; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.rocketcomponent.position.AxialMethod; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.MathUtil; - -/** - * The base class for most RASAero components. - */ -@XmlRootElement -@XmlType(name="RASAeroBasePartDTO") -@XmlAccessorType(XmlAccessType.FIELD) -@XmlTransient -public class BasePartDTO { - @XmlElement(name = RASAeroCommonConstants.PART_TYPE) - private String partType; - @XmlElement(name = RASAeroCommonConstants.LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double length; - @XmlElement(name = RASAeroCommonConstants.DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double diameter; - @XmlElement(name = RASAeroCommonConstants.LOCATION) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double location; - @XmlElement(name = RASAeroCommonConstants.COLOR) - private String color; - - @XmlTransient - private final RocketComponent component; - @XmlTransient - private final WarningSet warnings; - @XmlTransient - private final ErrorSet errors; - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - /** - * We need a default no-args constructor. - */ - public BasePartDTO() { - this.component = null; - this.warnings = null; - this.errors = null; - } - - protected BasePartDTO(RocketComponent component, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - this.component = component; - this.warnings = warnings; - this.errors = errors; - - if (component instanceof BodyTube) { - setPartType(RASAeroCommonConstants.BODY_TUBE); - setDiameter(((BodyTube) component).getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else if (component instanceof NoseCone) { - setPartType(RASAeroCommonConstants.NOSE_CONE); - NoseCone noseCone = (NoseCone) component; - if (noseCone.isFlipped()) { - throw new RASAeroExportException(trans.get("RASAeroExport.warning1")); - } - setDiameter(((NoseCone) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else if (component instanceof Transition) { - setPartType(RASAeroCommonConstants.TRANSITION); - // This is a bit strange: I would expect diameter to be the fore radius, since you also have a rearDiamter - // field for transitions, but okay - setDiameter(((Transition) component).getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else if (component instanceof AxialStage) { - setPartType(RASAeroCommonConstants.BOOSTER); - AxialStage stage = (AxialStage) component; - if (stage.getChildCount() == 0 || !(stage.getChild(0) instanceof BodyTube)) { - throw new RASAeroExportException(trans.get("RASAeroExport.warning2")); - } - setDiameter(stage.getBoundingRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error1"), component.getComponentName())); - } - - setLength(component.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setLocation(component.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(component.getColor())); - } - - public String getPartType() { - return partType; - } - - public void setPartType(String partType) { - this.partType = partType; - } - - public Double getLength() { - return length; - } - - public void setLength(Double length) throws RASAeroExportException { - if (MathUtil.equals(length, 0)) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error2"), component.getName())); - } - this.length = length; - } - - public Double getDiameter() { - return diameter; - } - - public void setDiameter(Double diameter) throws RASAeroExportException { - if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error3"), component.getName())); - } - this.diameter = diameter; - } - - public Double getLocation() { - return location; - } - - public void setLocation(Double location) { - this.location = location; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java deleted file mode 100644 index bb28be19c..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoattailDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = RASAeroCommonConstants.BOATTAIL) -@XmlAccessorType(XmlAccessType.FIELD) -public class BoattailDTO extends TransitionDTO { - /** - * We need a default no-args constructor. - */ - public BoattailDTO() { - super(); - } - - public BoattailDTO(Transition boattail, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - super(boattail, warnings, errors); - - setPartType(RASAeroCommonConstants.BOATTAIL); - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java deleted file mode 100644 index 371c38d3c..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTO.java +++ /dev/null @@ -1,192 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.BodyTube; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementRef; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.MathUtil; - -@XmlRootElement(name = RASAeroCommonConstants.BODY_TUBE) -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(propOrder = { - "partType", - "length", - "diameter", - "launchLugDiameter", - "launchLugLength", - "railGuideDiameter", - "railGuideHeight", - "launchShoeArea", - "location", - "color", - "boattailLength", - "boattailRearDiameter", - "boattailOffset", - "overhang", - "fin" -}) -public class BodyTubeDTO extends BasePartDTO implements BodyTubeDTOAdapter { - @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchLugDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchLugLength = 0d; - @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double railGuideDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_HEIGHT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double railGuideHeight = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_SHOE_AREA) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchShoeArea = 0d; // Currently not available in OR - - @XmlElement(name = RASAeroCommonConstants.BOATTAIL_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double boattailLength = 0d; - @XmlElement(name = RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double boattailRearDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.BOATTAIL_OFFSET) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double boattailOffset = 0d; - @XmlElement(name = RASAeroCommonConstants.OVERHANG) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double overhang = 0d; - - @XmlElementRef(name = RASAeroCommonConstants.FIN, type = FinDTO.class) - private FinDTO fin; - - - @XmlTransient - private final WarningSet warnings; - @XmlTransient - private final ErrorSet errors; - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - /** - * We need a default no-args constructor. - */ - public BodyTubeDTO() { - this.warnings = null; - this.errors = null; - } - - public BodyTubeDTO(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - super(bodyTube, warnings, errors); - this.warnings = warnings; - this.errors = errors; - applyBodyTubeSettings(bodyTube, warnings, errors); - } - - public Double getLaunchLugDiameter() { - return launchLugDiameter; - } - - public void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException { - if (MathUtil.equals(launchLugDiameter, 0)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error4")); - } - this.launchLugDiameter = launchLugDiameter; - } - - public Double getLaunchLugLength() { - return launchLugLength; - } - - public void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException { - if (MathUtil.equals(launchLugLength, 0)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error5")); - } - this.launchLugLength = launchLugLength; - } - - public Double getRailGuideDiameter() { - return railGuideDiameter; - } - - public void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException { - if (MathUtil.equals(railGuideDiameter, 0)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error6")); - } - this.railGuideDiameter = railGuideDiameter; - } - - public Double getRailGuideHeight() { - return railGuideHeight; - } - - public void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException { - if (MathUtil.equals(railGuideHeight, 0)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error7")); - } - this.railGuideHeight = railGuideHeight; - } - - public Double getLaunchShoeArea() { - return launchShoeArea; - } - - public void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException { - if (MathUtil.equals(launchShoeArea, 0)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error8")); - } - this.launchShoeArea = launchShoeArea; - } - - public Double getBoattailLength() { - return boattailLength; - } - - public void setBoattailLength(Double boattailLength) { - this.boattailLength = boattailLength; - } - - public Double getBoattailRearDiameter() { - return boattailRearDiameter; - } - - public void setBoattailRearDiameter(Double boattailRearDiameter) { - this.boattailRearDiameter = boattailRearDiameter; - } - - public Double getBoattailOffset() { - return boattailOffset; - } - - public void setBoattailOffset(Double boattailOffset) { - this.boattailOffset = boattailOffset; - } - - public Double getOverhang() { - return overhang; - } - - public void setOverhang(Double overhang) { - this.overhang = overhang; - } - - public FinDTO getFin() { - return fin; - } - - public void setFin(FinDTO fin) { - this.fin = fin; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java b/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java deleted file mode 100644 index 2fd2e8870..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/BodyTubeDTOAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.LaunchLug; -import net.sf.openrocket.rocketcomponent.Parachute; -import net.sf.openrocket.rocketcomponent.RailButton; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.MathUtil; -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; - -import javax.xml.bind.annotation.XmlTransient; - -public interface BodyTubeDTOAdapter { - @XmlTransient - Translator trans = Application.getTranslator(); - - default void applyBodyTubeSettings(BodyTube bodyTube, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - for (RocketComponent child : bodyTube.getChildren()) { - if (child instanceof TrapezoidFinSet) { - setFin(new FinDTO((TrapezoidFinSet) child, warnings, errors)); - } else if (child instanceof LaunchLug) { - if (!MathUtil.equals(getRailGuideDiameter(), 0) || !MathUtil.equals(getRailGuideHeight(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format(trans.get("RASAeroExport.warning3"), child.getName())); - continue; - } - if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format(trans.get("RASAeroExport.warning4"), child.getName())); - continue; - } - - LaunchLug lug = (LaunchLug) child; - setLaunchLugDiameter(lug.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - if (lug.getInstanceCount() == 2) { - setLaunchLugLength(lug.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else { - warnings.add(String.format(trans.get("RASAeroExport.warning5"), lug.getName())); - setLaunchLugLength(lug.getLength() * lug.getInstanceCount() / 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } - } else if (child instanceof RailButton) { - if (!MathUtil.equals(getLaunchLugDiameter(), 0) || !MathUtil.equals(getLaunchLugLength(), 0)) { // only one check on diameter or length should be sufficient, but just to be safe - warnings.add(String.format(trans.get("RASAeroExport.warning6"), child.getName())); - continue; - } - if (!MathUtil.equals(getLaunchShoeArea(), 0)) { - warnings.add(String.format(trans.get("RASAeroExport.warning7"), child.getName())); - continue; - } - - RailButton button = (RailButton) child; - setRailGuideDiameter(button.getOuterDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setRailGuideHeight(button.getTotalHeight() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - - if (button.getInstanceCount() != 2) { - warnings.add(String.format(trans.get("RASAeroExport.warning8"), button.getName(), button.getInstanceCount())); - } - } else if (child instanceof Parachute) { - // Do nothing, is handled by RecoveryDTO - } else { - warnings.add(String.format(trans.get("RASAeroExport.warning9"), child.getComponentName())); - } - } - } - - Double getLaunchLugDiameter(); - - void setLaunchLugDiameter(Double launchLugDiameter) throws RASAeroExportException; - - Double getLaunchLugLength(); - - void setLaunchLugLength(Double launchLugLength) throws RASAeroExportException; - - Double getRailGuideDiameter(); - - void setRailGuideDiameter(Double railGuideDiameter) throws RASAeroExportException; - - Double getRailGuideHeight(); - - void setRailGuideHeight(Double railGuideHeight) throws RASAeroExportException; - - Double getLaunchShoeArea(); - - void setLaunchShoeArea(Double launchShoeArea) throws RASAeroExportException; - - Double getBoattailLength(); - - void setBoattailLength(Double boattailLength) throws RASAeroExportException; - - Double getBoattailRearDiameter(); - - void setBoattailRearDiameter(Double boattailRearDiameter) throws RASAeroExportException; - - FinDTO getFin(); - - void setFin(FinDTO fin); -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java deleted file mode 100644 index 632926d66..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/BoosterDTO.java +++ /dev/null @@ -1,369 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.NoseCone; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementRef; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.rocketcomponent.SymmetricComponent; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; -import net.sf.openrocket.rocketcomponent.position.AxialMethod; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.MathUtil; - -@XmlRootElement(name = RASAeroCommonConstants.BOOSTER) -@XmlAccessorType(XmlAccessType.FIELD) -public class BoosterDTO implements BodyTubeDTOAdapter { - - @XmlElement(name = RASAeroCommonConstants.PART_TYPE) - private String partType; - @XmlElement(name = RASAeroCommonConstants.LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double length; - @XmlElement(name = RASAeroCommonConstants.DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double diameter; - @XmlElement(name = RASAeroCommonConstants.INSIDE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double insideDiameter; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchLugDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_LUG_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchLugLength = 0d; - @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double railGuideDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.RAIL_GUIDE_HEIGHT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double railGuideHeight = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_SHOE_AREA) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double launchShoeArea = 0d; // Currently not available in OR - @XmlElement(name = RASAeroCommonConstants.LOCATION) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double location; - @XmlElement(name = RASAeroCommonConstants.COLOR) - private String color; - @XmlElement(name = RASAeroCommonConstants.SHOULDER_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double shoulderLength; - @XmlElement(name = RASAeroCommonConstants.NOZZLE_EXIT_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double nozzleExitDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.BOATTAIL_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double boattailLength; - @XmlElement(name = RASAeroCommonConstants.BOATTAIL_REAR_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double boattailRearDiameter; - - @XmlElementRef(name = RASAeroCommonConstants.FIN, type = FinDTO.class) - private FinDTO fin; - - - @XmlTransient - private final RocketComponent component; - @XmlTransient - private final WarningSet warnings; - @XmlTransient - private final ErrorSet errors; - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - - /** - * We need a default, no-args constructor. - */ - public BoosterDTO() { - this.component = null; - this.warnings = null; - this.errors = null; - } - - protected BoosterDTO(Rocket rocket, AxialStage stage, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - this.component = stage; - this.warnings = warnings; - this.errors = errors; - - int stageNr = rocket.getChildPosition(stage); // Use this instead of stage.getStageNumber() in case there are parallel stages in the design - if (stageNr != 1 && stageNr != 2) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error9"), stageNr, stage.getName())); - } - - if (stage.getChildCount() == 0) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error10"), stage.getName())); - } - - RocketComponent firstChild = stage.getChild(0); - if (!(firstChild instanceof BodyTube) && - !(firstChild instanceof Transition && !(firstChild instanceof NoseCone))) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error11"), stage.getName())); - } - - final BodyTube firstTube; - if (firstChild instanceof Transition) { - if (stage.getChildCount() == 1 || !(stage.getChild(1) instanceof BodyTube)) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error12"), stage.getName())); - } - - Transition transition = (Transition) firstChild; - SymmetricComponent previousComponent = transition.getPreviousSymmetricComponent(); - if (previousComponent == null) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error13"), - firstChild.getName(), stage.getName())); - } - - if (!MathUtil.equals(transition.getForeRadius(), previousComponent.getAftRadius())) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error14"), - transition.getName(), stage.getName(), previousComponent.getName())); - } - - firstTube = (BodyTube) stage.getChild(1); - if (!MathUtil.equals(firstTube.getOuterRadius(), transition.getAftRadius())) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error15"), - firstTube.getName(), stage.getName(), transition.getName())); - } - - setShoulderLength(firstChild.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setInsideDiameter(transition.getForeRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } else { - firstTube = (BodyTube) stage.getChild(0); - } - - TrapezoidFinSet finSet = getFinSetFromBodyTube(firstTube); - - double tubeLength = firstTube.getLength(); - double finLocationOffset = 0; - // Aggregate same-sized body tubes - for (int i = stage.getChildPosition(firstTube) + 1; i < stage.getChildCount(); i++) { - RocketComponent comp = stage.getChild(i); - if (comp instanceof BodyTube && - MathUtil.equals(((BodyTube) comp).getOuterRadius(), firstTube.getOuterRadius())) { - // Aggregate the tubes by combining the lengths - tubeLength += comp.getLength(); - - // If no fin set in firstTube, add fin from new tube - if (finSet == null) { - finSet = getFinSetFromBodyTube((BodyTube) comp); - } - // We need an offset to the fin location, since the fin axial offset is referenced to its parent tube, - // which can be different from the bottom of the aggregate tubes - else { - finLocationOffset += comp.getLength(); - } - } else { - // If this booster is the last stage, and the last component is a transition, it could be a boattail - boolean isBoattail = (comp instanceof Transition && !(comp instanceof NoseCone)) && i == stage.getChildCount() - 1; - if (stageNr == rocket.getChildCount() - 1 && isBoattail) { - Transition transition = (Transition) comp; - setBoattailLength(transition.getLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setBoattailRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - break; - } - - String msg = String.format(trans.get("RASAeroExport.error31"), stage.getName()); - - if (isBoattail) { - msg = "" + msg + "
 " + trans.get("RASAeroExport.error32") + ""; - } - - errors.add(msg); - - break; - } - } - - applyBodyTubeSettings(firstTube, warnings, errors); - - if (finSet == null) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error16"), - firstTube.getName(), stage.getName())); - } - FinDTO finDTO = new FinDTO(finSet, warnings, errors); - double finLocation = finDTO.getLocation(); - finDTO.setLocation(finLocation + finLocationOffset * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setFin(finDTO); - - setPartType(RASAeroCommonConstants.BOOSTER); - setLength(tubeLength * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setDiameter(firstTube.getOuterRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setLocation(firstChild.getAxialOffset(AxialMethod.ABSOLUTE) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setColor(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_COLOR(firstTube.getColor())); - } - - private TrapezoidFinSet getFinSetFromBodyTube(BodyTube bodyTube) { - for (RocketComponent child : bodyTube.getChildren()) { - if (child instanceof TrapezoidFinSet) { - return (TrapezoidFinSet) child; - } - } - return null; - } - - public String getPartType() { - return partType; - } - - public void setPartType(String partType) { - this.partType = partType; - } - - public Double getLength() { - return length; - } - - public void setLength(Double length) { - if (MathUtil.equals(length, 0)) { - errors.add(String.format(trans.get("RASAeroExport.error17"), component.getName())); - return; - } - this.length = length; - } - - public Double getDiameter() { - return diameter; - } - - public void setDiameter(Double diameter) throws RASAeroExportException { - if (MathUtil.equals(diameter, 0)) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error18"), component.getName())); - } - this.diameter = diameter; - } - - public Double getInsideDiameter() { - return insideDiameter; - } - - public void setInsideDiameter(Double insideDiameter) throws RASAeroExportException { - if (MathUtil.equals(insideDiameter, 0)) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error19"), component.getName())); - } - this.insideDiameter = insideDiameter; - } - - public Double getLaunchLugDiameter() { - return launchLugDiameter; - } - - public void setLaunchLugDiameter(Double launchLugDiameter) { - this.launchLugDiameter = launchLugDiameter; - } - - public Double getLaunchLugLength() { - return launchLugLength; - } - - public void setLaunchLugLength(Double launchLugLength) { - this.launchLugLength = launchLugLength; - } - - public Double getRailGuideDiameter() { - return railGuideDiameter; - } - - public void setRailGuideDiameter(Double railGuideDiameter) { - this.railGuideDiameter = railGuideDiameter; - } - - public Double getRailGuideHeight() { - return railGuideHeight; - } - - public void setRailGuideHeight(Double railGuideHeight) { - this.railGuideHeight = railGuideHeight; - } - - public Double getLaunchShoeArea() { - return launchShoeArea; - } - - public void setLaunchShoeArea(Double launchShoeArea) { - this.launchShoeArea = launchShoeArea; - } - - public Double getLocation() { - return location; - } - - public void setLocation(Double location) { - this.location = location; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } - - public Double getShoulderLength() { - return shoulderLength; - } - - public void setShoulderLength(Double shoulderLength) { - this.shoulderLength = shoulderLength; - } - - public Double getNozzleExitDiameter() { - return nozzleExitDiameter; - } - - public void setNozzleExitDiameter(Double nozzleExitDiameter) { - this.nozzleExitDiameter = nozzleExitDiameter; - } - - public Double getBoattailLength() { - return boattailLength; - } - - public void setBoattailLength(Double boattailLength) throws RASAeroExportException { - if (boattailLength == 0) { - throw new RASAeroExportException(trans.get("RASAeroExport.error29")); - } - this.boattailLength = boattailLength; - } - - public Double getBoattailRearDiameter() { - return boattailRearDiameter; - } - - public void setBoattailRearDiameter(Double boattailRearDiameter) throws RASAeroExportException { - if (boattailRearDiameter == 0) { - throw new RASAeroExportException(trans.get("RASAeroExport.error30")); - } - this.boattailRearDiameter = boattailRearDiameter; - } - - public FinDTO getFin() { - return fin; - } - - public void setFin(FinDTO fin) { - this.fin = fin; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java deleted file mode 100644 index 9668dbba3..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/FinDTO.java +++ /dev/null @@ -1,168 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; -import net.sf.openrocket.rocketcomponent.position.AxialMethod; -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.startup.Application; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -@XmlRootElement(name = RASAeroCommonConstants.FIN) -@XmlAccessorType(XmlAccessType.FIELD) -public class FinDTO { - @XmlElement(name = RASAeroCommonConstants.FIN_COUNT) - private int count; - @XmlElement(name = RASAeroCommonConstants.FIN_CHORD) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double chord; - @XmlElement(name = RASAeroCommonConstants.FIN_SPAN) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double span; - @XmlElement(name = RASAeroCommonConstants.FIN_SWEEP_DISTANCE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sweepDistance; - @XmlElement(name = RASAeroCommonConstants.FIN_TIP_CHORD) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double tipChord; - @XmlElement(name = RASAeroCommonConstants.FIN_THICKNESS) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double thickness; - @XmlElement(name = RASAeroCommonConstants.FIN_LE_RADIUS) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double LERadius = 0d; // Leading edge radius - @XmlElement(name = RASAeroCommonConstants.LOCATION) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double location; - @XmlElement(name = RASAeroCommonConstants.AIRFOIL_SECTION) - private String airfoilSection; - @XmlElement(name = RASAeroCommonConstants.FIN_FX1) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double FX1 = 0d; - @XmlElement(name = RASAeroCommonConstants.FIN_FX3) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double FX3 = 0d; - - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - /** - * We need a default no-args constructor. - */ - public FinDTO() { - } - - public FinDTO(TrapezoidFinSet fin, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - int finCount = fin.getFinCount(); - if (finCount < 3 || finCount > 8) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error20"), fin.getName())); - } - - setCount(fin.getFinCount()); - setChord(fin.getRootChord() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setTipChord(fin.getTipChord() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setSpan(fin.getSpan() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setSweepDistance(fin.getSweep() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setThickness(fin.getThickness() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setAirfoilSection(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_FIN_CROSSSECTION(fin.getCrossSection(), warnings)); - setLocation((-fin.getAxialOffset(AxialMethod.BOTTOM) + fin.getLength()) * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public Double getChord() { - return chord; - } - - public void setChord(Double chord) { - this.chord = chord; - } - - public Double getSpan() { - return span; - } - - public void setSpan(Double span) { - this.span = span; - } - - public Double getSweepDistance() { - return sweepDistance; - } - - public void setSweepDistance(Double sweepDistance) { - this.sweepDistance = sweepDistance; - } - - public Double getTipChord() { - return tipChord; - } - - public void setTipChord(Double tipChord) { - this.tipChord = tipChord; - } - - public Double getThickness() { - return thickness; - } - - public void setThickness(Double thickness) { - this.thickness = thickness; - } - - public Double getLERadius() { - return LERadius; - } - - public void setLERadius(Double LERadius) { - this.LERadius = LERadius; - } - - public Double getLocation() { - return location; - } - - public void setLocation(Double location) { - this.location = location; - } - - public String getAirfoilSection() { - return airfoilSection; - } - - public void setAirfoilSection(String airfoilSection) { - this.airfoilSection = airfoilSection; - } - - public Double getFX1() { - return FX1; - } - - public void setFX1(Double FX1) { - this.FX1 = FX1; - } - - public Double getFX3() { - return FX3; - } - - public void setFX3(Double FX3) { - this.FX3 = FX3; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java deleted file mode 100644 index 88794e152..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/LaunchSiteDTO.java +++ /dev/null @@ -1,121 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.simulation.SimulationOptions; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.startup.Preferences; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -@XmlRootElement(name = RASAeroCommonConstants.LAUNCH_SITE) -@XmlAccessorType(XmlAccessType.FIELD) -public class LaunchSiteDTO { - - @XmlElement(name = RASAeroCommonConstants.LAUNCH_ALTITUDE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double altitude = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_PRESSURE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double pressure = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_ROD_ANGLE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double rodAngle = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_ROD_LENGTH) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double rodLength = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_TEMPERATURE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double temperature = 0d; - @XmlElement(name = RASAeroCommonConstants.LAUNCH_WIND_SPEED) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double windSpeed = 0d; - - /** - * We need a default, no-args constructor. - */ - public LaunchSiteDTO() { - } - - public LaunchSiteDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { - for (Simulation sim : document.getSimulations()) { - SimulationOptions options = sim.getSimulatedConditions(); - if (options == null) { - continue; - } - - setAltitude(options.getLaunchAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); - setPressure(options.getLaunchPressure() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_PRESSURE); - setTemperature(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TEMPERATURE(options.getLaunchTemperature())); - setRodAngle(options.getLaunchRodAngle() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ANGLE); - setRodLength(options.getLaunchRodLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setWindSpeed(options.getWindSpeedAverage() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SPEED); - return; - } - - // If we can't get settings from the sims, use the launch site settings from the preferences - Preferences prefs = Application.getPreferences(); - setAltitude(prefs.getLaunchAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); - setPressure(prefs.getLaunchPressure() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_PRESSURE); - setTemperature(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_TEMPERATURE(prefs.getLaunchTemperature())); - setRodAngle(prefs.getLaunchRodAngle() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ANGLE); - setRodLength(prefs.getLaunchRodLength() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - setWindSpeed(prefs.getWindSpeedAverage() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SPEED); - } - - public Double getAltitude() { - return altitude; - } - - public void setAltitude(Double altitude) { - this.altitude = altitude; - } - - public Double getPressure() { - return pressure; - } - - public void setPressure(Double pressure) { - this.pressure = pressure; - } - - public Double getRodAngle() { - return rodAngle; - } - - public void setRodAngle(Double rodAngle) { - this.rodAngle = rodAngle; - } - - public Double getRodLength() { - return rodLength; - } - - public void setRodLength(Double rodLength) { - this.rodLength = rodLength; - } - - public Double getTemperature() { - return temperature; - } - - public void setTemperature(Double temperature) { - this.temperature = temperature; - } - - public Double getWindSpeed() { - return windSpeed; - } - - public void setWindSpeed(Double windSpeed) { - this.windSpeed = windSpeed; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java deleted file mode 100644 index 267741701..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/NoseConeDTO.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.NoseCone; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants.NoseConeShapeSettings; - -@XmlRootElement(name = RASAeroCommonConstants.NOSE_CONE) -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(propOrder = { - "partType", - "length", - "diameter", - "shape", - "bluntRadius", - "location", - "color", - "powerLaw" -}) -public class NoseConeDTO extends BasePartDTO { - - @XmlElement(name = RASAeroCommonConstants.SHAPE) - private String shape; - @XmlElement(name = RASAeroCommonConstants.BLUNT_RADIUS) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double bluntRadius = 0d; - @XmlElement(name = RASAeroCommonConstants.POWER_LAW) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double powerLaw; - - /** - * We need a default no-args constructor. - */ - public NoseConeDTO() { - } - - public NoseConeDTO(NoseCone noseCone, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - super(noseCone, warnings, errors); - - NoseConeShapeSettings shapeSettings = - RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SHAPE(noseCone.getShapeType(), noseCone.getShapeParameter()); - - setShape(shapeSettings.getShape()); - Double shapeParameter = shapeSettings.getShapeParameter(); - if (shapeParameter != null) { - setPowerLaw(shapeParameter); - } - } - - public String getShape() { - return shape; - } - - public void setShape(String shape) { - this.shape = shape; - } - - public Double getPowerLaw() { - return powerLaw; - } - - public void setPowerLaw(Double powerLaw) { - this.powerLaw = powerLaw; - } - - public double getBluntRadius() { - return bluntRadius; - } - - public void setBluntRadius(double bluntRadius) { - this.bluntRadius = bluntRadius; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java deleted file mode 100644 index ef4420a83..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroDocumentDTO.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * The top level RASAero document. - */ -@XmlRootElement(name = RASAeroCommonConstants.RASAERO_DOCUMENT) -@XmlAccessorType(XmlAccessType.FIELD) -public class RASAeroDocumentDTO { - @XmlElement(name = RASAeroCommonConstants.FILE_VERSION) - private final String version = "2"; - - @XmlElement(name = RASAeroCommonConstants.ROCKET_DESIGN) - private RocketDesignDTO design; - - @XmlElement(name = RASAeroCommonConstants.LAUNCH_SITE) - private LaunchSiteDTO launchSite; - - @XmlElement(name = RASAeroCommonConstants.RECOVERY) - private RecoveryDTO recovery; - - - @XmlElement(name = RASAeroCommonConstants.MACH_ALT) - private String machAlt = ""; // Currently not implemented - - @XmlElement(name = RASAeroCommonConstants.SIMULATION_LIST) - private SimulationListDTO simulationList = null; - - /** - * Get the subordinate design DTO. - * - * @return the RocketDesignDTO - */ - public RocketDesignDTO getDesign() { - return design; - } - - public void setDesign(RocketDesignDTO theDesign) { - this.design = theDesign; - } - - public LaunchSiteDTO getLaunchSite() { - return launchSite; - } - - public void setLaunchSite(LaunchSiteDTO launchSite) { - this.launchSite = launchSite; - } - - public RecoveryDTO getRecovery() { - return recovery; - } - - public void setRecovery(RecoveryDTO recovery) { - this.recovery = recovery; - } - - public SimulationListDTO getSimulationList() { - return simulationList; - } - - public void setSimulationList(SimulationListDTO simulationList) { - this.simulationList = simulationList; - } - - public String getMachAlt() { - return this.machAlt; - } - - public void setMachAlt(String machAlt) { - this.machAlt = machAlt; - } - - public String getVersion() { - return version; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java b/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java deleted file mode 100644 index cdd902397..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/RASAeroSaver.java +++ /dev/null @@ -1,138 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.document.StorageOptions; -import net.sf.openrocket.file.RocketSaver; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.Rocket; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; - -/** - * This class is responsible for marshalling an OpenRocketDocument (OR design) to RASAero-compliant XML. - * Big thanks to hcraigmiller for testing and providing feedback. - * - * @author Sibo Van Gool - */ -public class RASAeroSaver extends RocketSaver { - /** - * The logger. - */ - private static final Logger log = LoggerFactory.getLogger(RASAeroSaver.class); - - public static class RASAeroExportException extends Exception { - public RASAeroExportException(String errorMessage) { - super(errorMessage); - } - } - - /** - * This method marshals an OpenRocketDocument (OR design) to RASAero-compliant XML. - * - * @param doc the OR design - * @return RASAero-compliant XML - */ - public String marshalToRASAero(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) { - try { - JAXBContext binder = JAXBContext.newInstance(RASAeroDocumentDTO.class); - Marshaller marshaller = binder.createMarshaller(); - marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - StringWriter sw = new StringWriter(); - - marshaller.marshal(toRASAeroDocumentDTO(doc, warnings, errors), sw); - return sw.toString(); - } catch (RASAeroExportException e) { - errors.add(e.getMessage()); - } catch (Exception e) { - log.error("Could not marshall a design to RASAero format. " + e.getMessage()); - throw new RuntimeException("Could not marshall a design to RASAero format. " + e.getMessage()); - } - - throw new RuntimeException("Could not marshall a design to RASAero format."); - } - - @Override - public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options, WarningSet warnings, ErrorSet errors) throws IOException { - log.info("Saving .CDX1 file"); - - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, StandardCharsets.UTF_8)); - writer.write(marshalToRASAero(doc, warnings, errors)); - writer.flush(); - } - - @Override - public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) { - return marshalToRASAero(doc, new WarningSet(), new ErrorSet()).length(); - } - - /** - * Root conversion method. It iterates over all subcomponents. - * - * @param doc the OR design - * @param warnings list to add export warnings to - * @param errors list to add export errors to - * @return a corresponding RASAero representation - */ - private RASAeroDocumentDTO toRASAeroDocumentDTO(OpenRocketDocument doc, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - RASAeroDocumentDTO rad = new RASAeroDocumentDTO(); - rad.setDesign(toRocketDesignDTO(doc.getRocket(), warnings, errors)); - rad.setLaunchSite(toLaunchSiteDTO(doc, warnings, errors)); - rad.setRecovery(toRecoveryDTO(doc.getRocket(), warnings, errors)); - rad.setSimulationList(toSimulationListDTO(doc, warnings, errors)); - - return rad; - } - - /** - * Create the RASAero rocket design (containing all the actual rocket components). - * @param rocket the OR rocket to export the components from - * @return the RASAero rocket design - */ - private RocketDesignDTO toRocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - return new RocketDesignDTO(rocket, warnings, errors); - } - - /** - * Create RASAero launch site settings. - * @param document document that contains simulations to take the launch site settings from - * @param warnings list to add export warnings to - * @param errors list to add export errors to - * @return the RASAero launch site settings - */ - private LaunchSiteDTO toLaunchSiteDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { - return new LaunchSiteDTO(document, warnings, errors); - } - - /** - * Create RASAero recovery settings. - * @param rocket rocket to fetch the recovery devices from - * @param warnings list to add export warnings to - * @param errors list to add export errors to - * @return the RASAero launch site settings - */ - private RecoveryDTO toRecoveryDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { - return new RecoveryDTO(rocket, warnings, errors); - } - - /** - * Create a list of simulations. - * @param document document that contains simulations - * @param warnings list to add export warnings to - * @param errors list to add export errors to - * @return the RASAero simulation list - */ - private SimulationListDTO toSimulationListDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { - return new SimulationListDTO(document, warnings, errors); - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java deleted file mode 100644 index 790d11ede..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/RecoveryDTO.java +++ /dev/null @@ -1,245 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; -import net.sf.openrocket.rocketcomponent.Parachute; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import java.util.LinkedList; -import java.util.List; - -@XmlRootElement(name = RASAeroCommonConstants.RECOVERY) -@XmlAccessorType(XmlAccessType.FIELD) -public class RecoveryDTO { - @XmlElement(name = RASAeroCommonConstants.RECOVERY_ALTITUDE + 1) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double altitude1 = 0d; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_ALTITUDE + 2) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double altitude2 = 0d; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_DEVICE_TYPE + 1) - private String deviceType1 = "None"; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_DEVICE_TYPE + 2) - private String deviceType2 = "None"; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT + 1) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean event1 = false; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT + 2) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean event2 = false; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_SIZE + 1) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double size1 = 0d; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_SIZE + 2) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double size2 = 0d; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT_TYPE + 1) - private String eventType1 = "None"; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_EVENT_TYPE + 2) - private String eventType2 = "None"; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_CD + 1) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double CD1 = 0d; - @XmlElement(name = RASAeroCommonConstants.RECOVERY_CD + 2) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double CD2 = 0d; - - - @XmlTransient - private static final Logger log = LoggerFactory.getLogger(RecoveryDTO.class); - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - /** - * We need a default, no-args constructor. - */ - public RecoveryDTO() { - } - - public RecoveryDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { - List parachutes = getParachutesFromRocket(rocket); - - switch (parachutes.size()) { - case 0: - log.debug("No parachutes present"); - break; - case 1: - configureRecoveryDevice1(parachutes.get(0), errors); - break; - case 2: - configureRecoveryDevice1(parachutes.get(0), errors); - configureRecoveryDevice2(parachutes.get(1), errors); - break; - } - } - - private List getParachutesFromRocket(Rocket rocket) { - List parachutes = new LinkedList<>(); - - for (int i = 0; i < Math.min(rocket.getChildCount(), 3); i++) { - AxialStage stage = (AxialStage) rocket.getChild(i); - - for (RocketComponent stageChild : stage.getChildren()) { - if (stageChild instanceof BodyTube) { - for (RocketComponent child : stageChild) { - if (child instanceof Parachute) { - parachutes.add((Parachute) child); - if (parachutes.size() == 2) { - return parachutes; - } - } - } - } - } - } - - return parachutes; - } - - private void configureRecoveryDevice1(Parachute device1, ErrorSet errors) { - setCD1(device1.getCD()); - setDeviceType1("Parachute"); - DeploymentConfiguration deployConfig = device1.getDeploymentConfigurations().getDefault(); - setAltitude1(deployConfig.getDeployAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); - if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.APOGEE) { - setEventType1(RASAeroCommonConstants.DEPLOYMENT_APOGEE); - } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { - setEventType1(RASAeroCommonConstants.RECOVERY_ALTITUDE); - } else { - errors.add(String.format(trans.get("RASAeroExport.error21"), - device1.getName(), deployConfig.getDeployEvent().toString())); - } - setEvent1(true); - setSize1(device1.getDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } - - private void configureRecoveryDevice2(Parachute device2, ErrorSet errors) { - setCD1(device2.getCD()); - setDeviceType2("Parachute"); - DeploymentConfiguration deployConfig = device2.getDeploymentConfigurations().getDefault(); - setAltitude2(deployConfig.getDeployAltitude() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_ALTITUDE); - if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.APOGEE) { - setEventType2(RASAeroCommonConstants.DEPLOYMENT_APOGEE); - } else if (deployConfig.getDeployEvent() == DeploymentConfiguration.DeployEvent.ALTITUDE) { - setEventType2(RASAeroCommonConstants.RECOVERY_ALTITUDE); - } else { - errors.add(String.format(trans.get("RASAeroExport.error21"), - device2.getName(), deployConfig.getDeployEvent().toString())); - } - setEvent2(true); - setSize2(device2.getDiameter() * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } - - public Double getAltitude1() { - return altitude1; - } - - public void setAltitude1(Double altitude1) { - this.altitude1 = altitude1; - } - - public Double getAltitude2() { - return altitude2; - } - - public void setAltitude2(Double altitude2) { - this.altitude2 = altitude2; - } - - public String getDeviceType1() { - return deviceType1; - } - - public void setDeviceType1(String deviceType1) { - this.deviceType1 = deviceType1; - } - - public String getDeviceType2() { - return deviceType2; - } - - public void setDeviceType2(String deviceType2) { - this.deviceType2 = deviceType2; - } - - public Boolean getEvent1() { - return event1; - } - - public void setEvent1(Boolean event1) { - this.event1 = event1; - } - - public Boolean getEvent2() { - return event2; - } - - public void setEvent2(Boolean event2) { - this.event2 = event2; - } - - public Double getSize1() { - return size1; - } - - public void setSize1(Double size1) { - this.size1 = size1; - } - - public Double getSize2() { - return size2; - } - - public void setSize2(Double size2) { - this.size2 = size2; - } - - public String getEventType1() { - return eventType1; - } - - public void setEventType1(String eventType1) { - this.eventType1 = eventType1; - } - - public String getEventType2() { - return eventType2; - } - - public void setEventType2(String eventType2) { - this.eventType2 = eventType2; - } - - public Double getCD1() { - return CD1; - } - - public void setCD1(Double CD1) { - this.CD1 = CD1; - } - - public Double getCD2() { - return CD2; - } - - public void setCD2(Double CD2) { - this.CD2 = CD2; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java deleted file mode 100644 index c9dd0c63b..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/RocketDesignDTO.java +++ /dev/null @@ -1,229 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.NoseCone; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.ArrayList; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementRef; -import javax.xml.bind.annotation.XmlElementRefs; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import java.util.List; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; - -@XmlAccessorType(XmlAccessType.FIELD) -public class RocketDesignDTO { - @XmlElementRefs({ - @XmlElementRef(name = RASAeroCommonConstants.BODY_TUBE, type = BodyTubeDTO.class), - @XmlElementRef(name = RASAeroCommonConstants.NOSE_CONE, type = NoseConeDTO.class), - @XmlElementRef(name = RASAeroCommonConstants.TRANSITION, type = TransitionDTO.class), - @XmlElementRef(name = RASAeroCommonConstants.BOOSTER, type = BoosterDTO.class) - }) - private final List externalPart = new ArrayList<>(); - - @XmlElementRefs({ - @XmlElementRef(name = RASAeroCommonConstants.BOOSTER, type = BoosterDTO.class), - }) - private final List boosters = new ArrayList<>(); - - @XmlElement(name = RASAeroCommonConstants.SURFACE_FINISH) - private String surface = RASAeroCommonConstants.FINISH_SMOOTH; - @XmlElement(name = RASAeroCommonConstants.CD) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double CD = 0d; - @XmlElement(name = RASAeroCommonConstants.MODIFIED_BARROWMAN) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean modifiedBarrowman = false; - @XmlElement(name = RASAeroCommonConstants.TURBULENCE) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean turbulence = false; - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_NOZZLE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sustainerNozzle = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_NOZZLE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1Nozzle = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_NOZZLE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster2Nozzle = 0d; - @XmlElement(name = RASAeroCommonConstants.USE_BOOSTER1) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean useBooster1 = false; - @XmlElement(name = RASAeroCommonConstants.USE_BOOSTER2) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean useBooster2 = false; - @XmlElement(name = RASAeroCommonConstants.COMMENTS) - private String comments = ""; - - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - public RocketDesignDTO(Rocket rocket, WarningSet warnings, ErrorSet errors) { - setComments(rocket.getComment()); - if (rocket.getChildCount() > 3) { - warnings.add(trans.get("RASAeroExport.warning10")); - } - setUseBooster1(rocket.getChildCount() >= 2); - setUseBooster2(rocket.getChildCount() == 3); - - AxialStage sustainer = rocket.getStage(0); - - // Export components from sustainer - for (int i = 0; i < sustainer.getChildCount(); i++) { - try { - RocketComponent component = sustainer.getChild(i); - if (i == 0 && !(component instanceof NoseCone)) { - errors.add(trans.get("RASAeroExport.error22")); - return; - } else if (i == 1 && !(component instanceof BodyTube || - (component instanceof Transition && !(component instanceof NoseCone) && (i == sustainer.getChildCount() - 1)))) { - errors.add(trans.get("RASAeroExport.error23")); - return; - } - if (component instanceof BodyTube) { - addExternalPart(new BodyTubeDTO((BodyTube) component, warnings, errors)); - } else if (component instanceof NoseCone) { - if (i != 0) { - errors.add(trans.get("RASAeroExport.error24")); - return; - } - addExternalPart(new NoseConeDTO((NoseCone) component, warnings, errors)); - // Set the global surface finish to that of the first nose cone - setSurface(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_SURFACE(((NoseCone) component).getFinish(), - warnings)); - } else if (component instanceof Transition) { - // If there is only a sustainer & this is the last child of the sustainer, it's a boattail - if (rocket.getChildCount() == 1 && (i == sustainer.getChildCount() - 1)) { - addExternalPart(new BoattailDTO((Transition) component, warnings, errors)); - } else { - addExternalPart(new TransitionDTO((Transition) component, warnings, errors)); - } - } else { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error33"), component.getComponentName())); - } - } catch (RASAeroExportException e) { - errors.add(e.getMessage()); - } - } - - // Export components from other stages - for (int i = 1; i < Math.min(rocket.getChildCount(), 3); i++) { - try { - addBooster(new BoosterDTO(rocket, (AxialStage) rocket.getChild(i), warnings, errors)); - } catch (RASAeroExportException e) { - errors.add(e.getMessage()); - } - } - } - - public String getSurface() { - return surface; - } - - public void setSurface(String surface) { - this.surface = surface; - } - - public double getCD() { - return CD; - } - - public void setCD(double CD) { - this.CD = CD; - } - - public boolean isModifiedBarrowman() { - return modifiedBarrowman; - } - - public void setModifiedBarrowman(boolean modifiedBarrowman) { - this.modifiedBarrowman = modifiedBarrowman; - } - - public Boolean isTurbulence() { - return turbulence; - } - - public void setTurbulence(Boolean turbulence) { - this.turbulence = turbulence; - } - - public Double getSustainerNozzle() { - return sustainerNozzle; - } - - public void setSustainerNozzle(Double sustainerNozzle) { - this.sustainerNozzle = sustainerNozzle; - } - - public Double getBooster1Nozzle() { - return booster1Nozzle; - } - - public void setBooster1Nozzle(Double booster1Nozzle) { - this.booster1Nozzle = booster1Nozzle; - } - - public Double getBooster2Nozzle() { - return booster2Nozzle; - } - - public void setBooster2Nozzle(Double booster2Nozzle) { - this.booster2Nozzle = booster2Nozzle; - } - - public Boolean isUseBooster1() { - return useBooster1; - } - - public void setUseBooster1(Boolean useBooster1) { - this.useBooster1 = useBooster1; - } - - public Boolean isUseBooster2() { - return useBooster2; - } - - public void setUseBooster2(Boolean useBooster2) { - this.useBooster2 = useBooster2; - } - - public String getComments() { - return comments; - } - - public void setComments(String comments) { - this.comments = comments; - } - - public List getExternalPart() { - return externalPart; - } - - public void addExternalPart(BasePartDTO theExternalPartDTO) { - externalPart.add(theExternalPartDTO); - } - - public List getBoosters() { - return boosters; - } - - public void addBooster(BoosterDTO boosterDTO) { - boosters.add(boosterDTO); - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java deleted file mode 100644 index b21a715b0..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationDTO.java +++ /dev/null @@ -1,496 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.file.rasaero.CustomBooleanAdapter; -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.RigidBody; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.MotorConfiguration; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.FlightConfigurationId; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; -import net.sf.openrocket.startup.Application; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import java.util.List; -import java.util.Map; - -@XmlRootElement(name = RASAeroCommonConstants.SIMULATION) -@XmlAccessorType(XmlAccessType.FIELD) -public class SimulationDTO { - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_ENGINE) - private String sustainerEngine; - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_LAUNCH_WT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sustainerLaunchWt = 0d; - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_NOZZLE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sustainerNozzleDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_CG) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sustainerCG = 0d; - @XmlElement(name = RASAeroCommonConstants.SUSTAINER_IGNITION_DELAY) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double sustainerIgnitionDelay = 0d; - - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_ENGINE) - private String booster1Engine; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_LAUNCH_WT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1LaunchWt = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_SEPARATION_DELAY) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1SeparationDelay = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_IGNITION_DELAY) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1IgnitionDelay = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_CG) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1CG = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER1_NOZZLE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster1NozzleDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.INCLUDE_BOOSTER1) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean includeBooster1 = false; - - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_ENGINE) - private String booster2Engine; - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_LAUNCH_WT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster2LaunchWt = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_SEPARATION_DELAY) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster2Delay = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_CG) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster2CG = 0d; - @XmlElement(name = RASAeroCommonConstants.BOOSTER2_NOZZLE_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double booster2NozzleDiameter = 0d; - @XmlElement(name = RASAeroCommonConstants.INCLUDE_BOOSTER2) - @XmlJavaTypeAdapter(CustomBooleanAdapter.class) - private Boolean includeBooster2 = false; - - @XmlElement(name = RASAeroCommonConstants.FLIGHT_TIME) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double flightTime = 0d; - @XmlElement(name = RASAeroCommonConstants.TIME_TO_APOGEE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double timetoApogee = 0d; - @XmlElement(name = RASAeroCommonConstants.MAX_ALTITUDE) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double maxAltitude = 0d; - @XmlElement(name = RASAeroCommonConstants.MAX_VELOCITY) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double maxVelocity = 0d; - @XmlElement(name = RASAeroCommonConstants.OPTIMUM_WT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double optimumWt = 0d; - @XmlElement(name = RASAeroCommonConstants.OPTIMUM_MAX_ALT) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double optimumMaxAlt = 0d; - - @XmlTransient - private static final Translator trans = Application.getTranslator(); - - /** - * We need a default, no-args constructor. - */ - public SimulationDTO() { - } - - /** - * RASAero Simulation object. - * @param rocket the rocket - * @param simulation the simulation to convert - * @param mounts a map of stages and their corresponding motor mount (only 1 mount per stage allowed) - * if a motor mount is null, it means that stage does not have any motors, but mass/CG export should still take place - * @param motors a list of RASAero motors - * @param warnings a list to add export warnings to - * @param errors a list to add export errors to - */ - public SimulationDTO(Rocket rocket, Simulation simulation, Map mounts, List motors, - WarningSet warnings, ErrorSet errors) { - String simulationName = simulation != null ? simulation.getName() : "DEFAULT"; - FlightConfigurationId fcid = simulation != null ? simulation.getFlightConfigurationId() : null; - - if (simulation != null && fcid == null) { - warnings.add(String.format(trans.get("RASAeroExport.warning11"), simulationName)); - return; - } - - if (mounts.isEmpty()) { - warnings.add(String.format(trans.get("RASAeroExport.warning12"), simulationName)); - return; - } - - // Get sustainer motor mass - MotorMount sustainerMount = mounts.get((AxialStage) rocket.getChild(0)); - Motor sustainerMotor = null; - double sustainerMotorMass = 0; - if (sustainerMount != null) { - MotorConfiguration sustainerConfig = sustainerMount.getMotorConfig(fcid); - sustainerMotor = sustainerConfig.getMotor(); - sustainerMotorMass = sustainerMotor != null ? sustainerMotor.getLaunchMass() : 0; - } - - for (Map.Entry mountSet : mounts.entrySet()) { - AxialStage stage = mountSet.getKey(); - MotorMount mount = mountSet.getValue(); - if (stage == null) { - continue; - } - - // Get the motor info for this stage - MotorConfiguration motorConfig = mount != null ? mount.getMotorConfig(fcid) : null; - Motor motor = null; - StageSeparationConfiguration separationConfig = null; - double motorMass = 0; - if (motorConfig != null) { - motor = motorConfig.getMotor(); - motorMass = motor != null ? motor.getLaunchMass() : 0; - separationConfig = stage.getSeparationConfigurations().get(fcid); - } - int stageNr = rocket.getChildPosition(stage); - - // Add friendly reminder to user - if (motor == null) { - warnings.add(String.format(trans.get("RASAeroExport.warning13"), stage.getName())); - } - - // Add the simulation info for each stage - FlightConfiguration CGCalcConfig = new FlightConfiguration(rocket); - RigidBody calc; - double ignitionDelay, totalCG, separationDelay; - switch (stageNr) { - // Sustainer - case 0: - setSustainerEngine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); - - // Calculate mass & CG of sustainer - CGCalcConfig.setOnlyStage(0); - calc = MassCalculator.calculateStructure(CGCalcConfig); - - // Set mass - double sustainerMass = calc.getMass() + motorMass; - setSustainerLaunchWt(sustainerMass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - - // Set CG - double sustainerCG = calc.getCM().x; // = sutainer CG with no motors - sustainerCG = addMotorCGToStageCG(sustainerCG, calc.getMass(), mount, motor, fcid); - setSustainerCG(sustainerCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - - // Set ignition delay - ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; - setSustainerIgnitionDelay(ignitionDelay); - - break; - // Booster 1 - case 1: - setBooster1Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); - - // Calculate mass & CG of sustainer + booster 1 combined - CGCalcConfig.setOnlyStage(0); - for (int i = 1; i <= stage.getStageNumber(); i++) { - CGCalcConfig._setStageActive(i, true); - } - calc = MassCalculator.calculateStructure(CGCalcConfig); - - // Set mass - double booster1Mass = calc.getMass() + motorMass + sustainerMotorMass; - setBooster1LaunchWt(booster1Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - - // Set CG - totalCG = calc.getCM().x; // = sustainer + booster 1 CG with no sustainer & booster & motors - totalCG = addMotorCGToStageCG(totalCG, calc.getMass(), sustainerMount, sustainerMotor, fcid); - totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass, mount, motor, fcid); - setBooster1CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - - // Set ignition delay - ignitionDelay = motorConfig != null ? motorConfig.getIgnitionDelay() : 0; - setBooster1IgnitionDelay(ignitionDelay); - - // Set separation delay - separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; - setBooster1SeparationDelay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) - - setIncludeBooster1(mount != null && mount.isMotorMount()); - - break; - // Booster 2 - case 2: - setBooster2Engine(RASAeroCommonConstants.OPENROCKET_TO_RASAERO_MOTOR(motors, motor, warnings)); - - // Calculate mass & CG of sustainer + booster 1 + booster 2 combined - CGCalcConfig.setOnlyStage(0); - for (int i = 1; i <= stage.getStageNumber(); i++) { - CGCalcConfig._setStageActive(i, true); - } - calc = MassCalculator.calculateStructure(CGCalcConfig); - - // Get booster1 motor mass - double booster1MotorMass = 0; - MotorMount booster1Mount = mounts.get((AxialStage) rocket.getChild(1)); - Motor booster1Motor = null; - if (booster1Mount != null) { - MotorConfiguration booster1Config = booster1Mount.getMotorConfig(fcid); - booster1Motor = booster1Config.getMotor(); - booster1MotorMass = booster1Motor != null ? booster1Motor.getLaunchMass() : 0; - } - - // Set mass - double booster2Mass = calc.getMass() + motorMass + sustainerMotorMass + booster1MotorMass; - setBooster2LaunchWt(booster2Mass * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_WEIGHT); - - // Set CG - totalCG = calc.getCM().x; // CG of sustainer + booster 1 + booster 2 combined, with no sustainer, booster1 and booster2 motors! - totalCG = addMotorCGToStageCG(totalCG, calc.getMass(), sustainerMount, sustainerMotor, fcid); - totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass, booster1Mount, booster1Motor, fcid); - totalCG = addMotorCGToStageCG(totalCG, calc.getMass() + sustainerMotorMass + booster1MotorMass, mount, motor, fcid); - setBooster2CG(totalCG * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - - // Set separation delay - separationDelay = separationConfig != null ? separationConfig.getSeparationDelay() : 0; - setBooster2Delay(separationDelay); // TODO: this could be handled a bit better (look at separation delay, upper stage ignition event etc.) - - setIncludeBooster2(mount != null && mount.isMotorMount()); - - break; - // Invalid - default: - errors.add(String.format(trans.get("RASAeroExport.error25"), stageNr, simulationName)); - } - } - } - - /** - * Combines the stage CG with the CG of the motor in that stage. - * @param stageCG The CG of the stage - * @param mount The motor mount of the stage - * @param motor The motor in the stage - * @return The combined CG - */ - private double addMotorCGToStageCG(double stageCG, double stageMass, MotorMount mount, Motor motor, FlightConfigurationId fcid) { - if (mount == null || !(motor instanceof ThrustCurveMotor)) { - return stageCG; - } - - // Calculate the motor CG - double motorPositionXRel = mount.getMotorPosition(fcid).x; // Motor position relative to the mount - double mountLocationX = mount.getLocations()[0].x; - double motorLocationX = mountLocationX + motorPositionXRel; - double motorCG = ((ThrustCurveMotor) motor).getCGPoints()[0].x + motorLocationX; - - double motorMass = motor.getLaunchMass(); - - return (stageCG*stageMass + motorCG*motorMass) / (stageMass + motorMass); - } - - - public String getSustainerEngine() { - return sustainerEngine; - } - - public void setSustainerEngine(String sustainerEngine) { - this.sustainerEngine = sustainerEngine; - } - - public Double getSustainerLaunchWt() { - return sustainerLaunchWt; - } - - public void setSustainerLaunchWt(Double sustainerLaunchWt) { - this.sustainerLaunchWt = sustainerLaunchWt; - } - - public Double getSustainerNozzleDiameter() { - return sustainerNozzleDiameter; - } - - public void setSustainerNozzleDiameter(Double sustainerNozzleDiameter) { - this.sustainerNozzleDiameter = sustainerNozzleDiameter; - } - - public Double getSustainerCG() { - return sustainerCG; - } - - public void setSustainerCG(Double sustainerCG) { - this.sustainerCG = sustainerCG; - } - - public Double getSustainerIgnitionDelay() { - return sustainerIgnitionDelay; - } - - public void setSustainerIgnitionDelay(Double sustainerIgnitionDelay) { - this.sustainerIgnitionDelay = sustainerIgnitionDelay; - } - - public String getBooster1Engine() { - return booster1Engine; - } - - public void setBooster1Engine(String booster1Engine) { - this.booster1Engine = booster1Engine; - } - - public Double getBooster1LaunchWt() { - return booster1LaunchWt; - } - - public void setBooster1LaunchWt(Double booster1LaunchWt) { - this.booster1LaunchWt = booster1LaunchWt; - } - - public Double getBooster1SeparationDelay() { - return booster1SeparationDelay; - } - - public void setBooster1SeparationDelay(Double booster1SeparationDelay) { - this.booster1SeparationDelay = booster1SeparationDelay; - } - - public Double getBooster1IgnitionDelay() { - return booster1IgnitionDelay; - } - - public void setBooster1IgnitionDelay(Double booster1IgnitionDelay) { - this.booster1IgnitionDelay = booster1IgnitionDelay; - } - - public Double getBooster1CG() { - return booster1CG; - } - - public void setBooster1CG(Double booster1CG) { - this.booster1CG = booster1CG; - } - - public Double getBooster1NozzleDiameter() { - return booster1NozzleDiameter; - } - - public void setBooster1NozzleDiameter(Double booster1NozzleDiameter) { - this.booster1NozzleDiameter = booster1NozzleDiameter; - } - - public Boolean getIncludeBooster1() { - return includeBooster1; - } - - public void setIncludeBooster1(Boolean includeBooster1) { - this.includeBooster1 = includeBooster1; - } - - public String getBooster2Engine() { - return booster2Engine; - } - - public void setBooster2Engine(String booster2Engine) { - this.booster2Engine = booster2Engine; - } - - public Double getBooster2LaunchWt() { - return booster2LaunchWt; - } - - public void setBooster2LaunchWt(Double booster2LaunchWt) { - this.booster2LaunchWt = booster2LaunchWt; - } - - public Double getBooster2Delay() { - return booster2Delay; - } - - public void setBooster2Delay(Double booster2Delay) { - this.booster2Delay = booster2Delay; - } - - public Double getBooster2CG() { - return booster2CG; - } - - public void setBooster2CG(Double booster2CG) { - this.booster2CG = booster2CG; - } - - public Double getBooster2NozzleDiameter() { - return booster2NozzleDiameter; - } - - public void setBooster2NozzleDiameter(Double booster2NozzleDiameter) { - this.booster2NozzleDiameter = booster2NozzleDiameter; - } - - public Boolean getIncludeBooster2() { - return includeBooster2; - } - - public void setIncludeBooster2(Boolean includeBooster2) { - this.includeBooster2 = includeBooster2; - } - - public Double getFlightTime() { - return flightTime; - } - - public void setFlightTime(Double flightTime) { - this.flightTime = flightTime; - } - - public Double getTimetoApogee() { - return timetoApogee; - } - - public void setTimetoApogee(Double timetoApogee) { - this.timetoApogee = timetoApogee; - } - - public Double getMaxAltitude() { - return maxAltitude; - } - - public void setMaxAltitude(Double maxAltitude) { - this.maxAltitude = maxAltitude; - } - - public Double getMaxVelocity() { - return maxVelocity; - } - - public void setMaxVelocity(Double maxVelocity) { - this.maxVelocity = maxVelocity; - } - - public Double getOptimumWt() { - return optimumWt; - } - - public void setOptimumWt(Double optimumWt) { - this.optimumWt = optimumWt; - } - - public Double getOptimumMaxAlt() { - return optimumMaxAlt; - } - - public void setOptimumMaxAlt(Double optimumMaxAlt) { - this.optimumMaxAlt = optimumMaxAlt; - } -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java deleted file mode 100644 index 5eb30bd42..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/SimulationListDTO.java +++ /dev/null @@ -1,102 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.file.rasaero.RASAeroMotorsLoader; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -@XmlRootElement(name = RASAeroCommonConstants.SIMULATION_LIST) -@XmlAccessorType(XmlAccessType.FIELD) -public class SimulationListDTO { - @XmlElement(name = RASAeroCommonConstants.SIMULATION) - private final List simulations = new LinkedList<>(); - - /** - * We need a default, no-args constructor. - */ - public SimulationListDTO() { - } - - public SimulationListDTO(OpenRocketDocument document, WarningSet warnings, ErrorSet errors) { - Map mounts = new HashMap<>(); - Rocket rocket = document.getRocket(); - - // Fetch all the motor mounts from the design - for (RocketComponent child : rocket.getChildren()) { - AxialStage stage = (AxialStage) child; - if (mounts.containsKey(stage)) { - continue; - } - for (RocketComponent stageChild : stage.getChildren()) { - if (stageChild instanceof BodyTube) { - // First check if the body tube itself has a motor - if (((BodyTube) stageChild).hasMotor()) { - mounts.put(stage, (BodyTube) stageChild); - break; - } - // Then check if it has an inner tube with a motor - else { - boolean addedMount = false; - for (RocketComponent tubeChild : stageChild.getChildren()) { - if (tubeChild instanceof MotorMount && ((MotorMount) tubeChild).hasMotor()) { - mounts.put(stage, (MotorMount) tubeChild); - addedMount = true; - break; - } - } - if (addedMount) { - break; - } - } - } - } - - // If at this point, we still don't have a mount, there is probably a mount without a motor. - // In that case, add a null mount, so that mass/CG export happens. - if (!mounts.containsKey(stage)) { - mounts.put(stage, null); - } - } - - // Load all RASAero motors - List motors = RASAeroMotorsLoader.loadAllRASAeroMotors(warnings); - - // Add all the simulations - for (Simulation simulation : document.getSimulations()) { - addSimulation(new SimulationDTO(rocket, simulation, mounts, motors, warnings, errors)); - } - - // If there are no simulations, add a default simulation (to have the mass/CG export) - if (document.getSimulations().size() == 0) { - addSimulation(new SimulationDTO(rocket, null, mounts, motors, warnings, errors)); - } - - motors.clear(); - } - - public List getSimulations() { - return simulations; - } - - public void addSimulation(SimulationDTO simulation) { - simulations.add(simulation); - } - -} diff --git a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java b/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java deleted file mode 100644 index 26aa2bd16..000000000 --- a/core/src/net/sf/openrocket/file/rasaero/export/TransitionDTO.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.sf.openrocket.file.rasaero.export; - -import net.sf.openrocket.file.rasaero.CustomDoubleAdapter; -import net.sf.openrocket.file.rasaero.RASAeroCommonConstants; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.WarningSet; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import net.sf.openrocket.file.rasaero.export.RASAeroSaver.RASAeroExportException; -import net.sf.openrocket.rocketcomponent.SymmetricComponent; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.MathUtil; - -@XmlRootElement(name = RASAeroCommonConstants.TRANSITION) -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(propOrder = { - "partType", - "length", - "diameter", - "rearDiameter", - "location", - "color" -}) -@XmlSeeAlso({BoattailDTO.class}) -public class TransitionDTO extends BasePartDTO { - - @XmlElement(name = RASAeroCommonConstants.REAR_DIAMETER) - @XmlJavaTypeAdapter(CustomDoubleAdapter.class) - private Double rearDiameter; - - @XmlTransient - private static final Translator trans = Application.getTranslator(); - @XmlTransient - private static Transition component = null; - - /** - * We need a default no-args constructor. - */ - public TransitionDTO() { - } - - public TransitionDTO(Transition transition, WarningSet warnings, ErrorSet errors) throws RASAeroExportException { - super(transition, warnings, errors); - - component = transition; - - if (!transition.getShapeType().equals(Transition.Shape.CONICAL)) { - throw new RASAeroExportException(trans.get("RASAeroExport.error26")); - } - - SymmetricComponent previousComp = transition.getPreviousSymmetricComponent(); - if (previousComp == null) { - throw new RASAeroExportException(String.format(trans.get("RASAeroExport.error27"), transition.getName())); - } - if (!MathUtil.equals(transition.getForeRadius(), previousComp.getAftRadius())) { - throw new RASAeroExportException( - String.format(trans.get("RASAeroExport.error28"), - transition.getName(), previousComp.getAftRadius(), transition.getForeRadius())); - } - - setRearDiameter(transition.getAftRadius() * 2 * RASAeroCommonConstants.OPENROCKET_TO_RASAERO_LENGTH); - } - - public Double getRearDiameter() { - return rearDiameter; - } - - public void setRearDiameter(Double rearDiameter) throws RASAeroExportException { - if (rearDiameter < 0.0001) { - throw new RASAeroExportException(String.format("'%s' rear diameter must be greater than 0.0001 inch", component)); - } - this.rearDiameter = rearDiameter; - } -} diff --git a/core/src/net/sf/openrocket/logging/Error.java b/core/src/net/sf/openrocket/logging/Error.java deleted file mode 100644 index 69ba76dd6..000000000 --- a/core/src/net/sf/openrocket/logging/Error.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.sf.openrocket.logging; - -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.startup.Application; - -/** - * An error message wrapper. - */ -public abstract class Error extends Message { - private static final Translator trans = Application.getTranslator(); - - /** - * @return an Error with the specific text. - */ - public static Error fromString(String text) { - return new Error.Other(text); - } - - - ///////////// Specific Error classes ///////////// - - - /** - * An unspecified error type. This error type holds a String - * describing it. Two errors of this type are considered equal if the strings - * are identical. - */ - public static class Other extends Error { - private final String description; - - public Other(String description) { - this.description = description; - } - - @Override - public String toString() { - return description; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Other)) - return false; - - Other o = (Other) other; - return (o.description.equals(this.description)); - } - - @Override - public int hashCode() { - return description.hashCode(); - } - - @Override - public boolean replaceBy(Message other) { - return false; - } - } -} diff --git a/core/src/net/sf/openrocket/logging/ErrorSet.java b/core/src/net/sf/openrocket/logging/ErrorSet.java deleted file mode 100644 index 76ea8977a..000000000 --- a/core/src/net/sf/openrocket/logging/ErrorSet.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.sf.openrocket.logging; - -import net.sf.openrocket.util.BugException; - -public class ErrorSet extends MessageSet { - /** - * Add an Error with the specified text to the set. The Error object - * is created using the {@link Error#fromString(String)} method. If an error of the - * same type exists in the set, the error that is left in the set is defined by the - * method {@link Error#replaceBy(Message)}. - * - * @param s the message text. - * @throws IllegalStateException if this message set has been made immutable. - */ - public boolean add(String s) { - mutable.check(); - return add(Error.fromString(s)); - } - - @Override - public ErrorSet clone() { - try { - ErrorSet newSet = (ErrorSet) super.clone(); - newSet.messages = this.messages.clone(); - newSet.mutable = this.mutable.clone(); - return newSet; - - } catch (CloneNotSupportedException e) { - throw new BugException("CloneNotSupportedException occurred, report bug!", e); - } - } -} diff --git a/core/src/net/sf/openrocket/logging/Message.java b/core/src/net/sf/openrocket/logging/Message.java deleted file mode 100644 index 9b813c1a8..000000000 --- a/core/src/net/sf/openrocket/logging/Message.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.sf.openrocket.logging; - -/** - * Baseclass for logging messages (warnings, errors...) - */ -public abstract class Message { - /** - * @return a Message with the specific text. - */ - public static Message fromString(String text) { - return new Warning.Other(text); - } - - /** - * Return true if the other warning should replace - * this message. The method should return true if the other - * indicates a "worse" condition than the current warning. - * - * @param other the message to compare to - * @return whether this message should be replaced - */ - public abstract boolean replaceBy(Message other); - - - /** - * Two Messages are by default considered equal if they are of - * the same class. Therefore only one instance of a particular message type - * is stored in a {@link MessageSet}. Subclasses may override this method for - * more specific functionality. - */ - @Override - public boolean equals(Object o) { - return o != null && (o.getClass() == this.getClass()); - } - - /** - * A hashCode method compatible with the equals method. - */ - @Override - public int hashCode() { - return this.getClass().hashCode(); - } -} diff --git a/core/src/net/sf/openrocket/logging/MessageSet.java b/core/src/net/sf/openrocket/logging/MessageSet.java deleted file mode 100644 index 064c52cf5..000000000 --- a/core/src/net/sf/openrocket/logging/MessageSet.java +++ /dev/null @@ -1,123 +0,0 @@ -package net.sf.openrocket.logging; - -import net.sf.openrocket.util.ArrayList; -import net.sf.openrocket.util.BugException; -import net.sf.openrocket.util.Monitorable; -import net.sf.openrocket.util.Mutable; - -import java.util.AbstractSet; -import java.util.Iterator; - -/** - * A set that contains multiple Messages. When adding a - * {@link Message} to this set, the contents is checked for a message of the - * same type. If one is found, then the message left in the set is determined - * by the method {@link Message#replaceBy(Message)}. - *

- * A MessageSet can be made immutable by calling {@link #immute()}. - * - * @author Sampo Niskanen - */ -public abstract class MessageSet extends AbstractSet implements Cloneable, Monitorable { - /** the actual array of messages */ - protected ArrayList messages = new ArrayList<>(); - - protected Mutable mutable = new Mutable(); - private int modID = 0; - - /** - * Add a Message to the set. If a message of the same type - * exists in the set, the message that is left in the set is defined by the - * method {@link Message#replaceBy(Message)}. - * - * @throws IllegalStateException if this message set has been made immutable. - */ - @Override - public boolean add(E m) { - mutable.check(); - - modID++; - int index = messages.indexOf(m); - - if (index < 0) { - messages.add(m); - return false; - } - - E old = messages.get(index); - if (old.replaceBy(m)) { - messages.set(index, m); - } - - return true; - } - - /** - * Add a Message with the specified text to the set. - * - * @param s the message text. - * @throws IllegalStateException if this message set has been made immutable. - */ - public abstract boolean add(String s); - - /** - * Add a Message of the specified type with the specified discriminator to the - * set. - * @param m the message - * @param d the extra discriminator - * - */ - public boolean add (E m, String d) { - return this.add(m.toString() + ": \"" + d + "\""); - } - - @Override - public Iterator iterator() { - final Iterator iterator = messages.iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public E next() { - return iterator.next(); - } - - @Override - public void remove() { - mutable.check(); - iterator.remove(); - } - }; - } - - @Override - public int size() { - return messages.size(); - } - - - public void immute() { - mutable.immute(); - } - - - @Override - public String toString() { - StringBuilder s = new StringBuilder(); - - for (Message m : messages) { - if (s.length() > 0) - s.append(","); - s.append(m.toString()); - } - return "Messages[" + s + "]"; - } - - @Override - public int getModID() { - return modID; - } -} diff --git a/core/src/net/sf/openrocket/logging/Warning.java b/core/src/net/sf/openrocket/logging/Warning.java deleted file mode 100644 index 9d1bc0421..000000000 --- a/core/src/net/sf/openrocket/logging/Warning.java +++ /dev/null @@ -1,392 +0,0 @@ -package net.sf.openrocket.logging; - -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.simulation.FlightEvent; -import net.sf.openrocket.unit.UnitGroup; - -/** - * A warning message wrapper. - */ -public abstract class Warning extends Message { - - /** support to multiple languages warning */ - private static final Translator trans = Application.getTranslator(); - - /** - * @return a Warning with the specific text. - */ - public static Warning fromString(String text) { - return new Warning.Other(text); - } - - - - ///////////// Specific warning classes ///////////// - - - /** - * A Warning indicating a large angle of attack was encountered. - * - * @author Sampo Niskanen - */ - public static class LargeAOA extends Warning { - private final double aoa; - - /** - * Sole constructor. The argument is the AOA that caused this warning. - * - * @param aoa the angle of attack that caused this warning - */ - public LargeAOA(double aoa) { - this.aoa = aoa; - } - - @Override - public String toString() { - if (Double.isNaN(aoa)) - //// Large angle of attack encountered. - return trans.get("Warning.LargeAOA.str1"); - //// Large angle of attack encountered ( - return (trans.get("Warning.LargeAOA.str2") + - UnitGroup.UNITS_ANGLE.getDefaultUnit().toString(aoa) + ")."); - } - - @Override - public boolean replaceBy(Message other) { - if (!(other instanceof LargeAOA)) - return false; - - LargeAOA o = (LargeAOA) other; - if (Double.isNaN(this.aoa)) // If this has value NaN then replace - return true; - return (o.aoa > this.aoa); - } - } - - /** - * A Warning indicating recovery device deployment at high speed was encountered. - * - * @author Craig Earls - */ - public static class HighSpeedDeployment extends Warning { - private final double recoverySpeed; - - /** - * Sole constructor. The argument is the speed that caused this warning. - * - * @param speed the speed that caused this warning - */ - public HighSpeedDeployment(double speed) { - this.recoverySpeed = speed; - } - - @Override - public String toString() { - if (Double.isNaN(recoverySpeed)) { - return trans.get("Warning.RECOVERY_HIGH_SPEED"); - } - return trans.get("Warning.RECOVERY_HIGH_SPEED") + " (" + UnitGroup.UNITS_VELOCITY.toStringUnit(recoverySpeed) + ")"; - } - - @Override - public boolean replaceBy(Message other) { - return false; - } - } - - /** - * A Warning indicating flight events occurred after ground hit - * - */ - public static class EventAfterLanding extends Warning { - private final FlightEvent event; - - /** - * Sole constructor. The argument is an event which has occurred after landing - * - * @param _event the event that caused this warning - */ - public EventAfterLanding(FlightEvent _event) { - this.event = _event; - } - - // I want a warning on every event that occurs after we land, - // so severity of problem is clear to the user - @Override - public boolean equals(Object o) { - return false; - } - - - @Override - public String toString() { - return trans.get("Warning.EVENT_AFTER_LANDING") + event.getType(); - } - - @Override - public boolean replaceBy(Message other) { - return false; - } - } - - public static class MissingMotor extends Warning { - - private Motor.Type type = null; - private String manufacturer = null; - private String designation = null; - private String digest = null; - private double diameter = Double.NaN; - private double length = Double.NaN; - private double delay = Double.NaN; - - @Override - public String toString() { - String str = "No motor with designation '" + designation + "'"; - if (manufacturer != null) - str += " for manufacturer '" + manufacturer + "'"; - str += " found."; - return str; - } - - public Motor.Type getType() { - return type; - } - - - public void setType(Motor.Type type) { - this.type = type; - } - - - public String getManufacturer() { - return manufacturer; - } - - - public void setManufacturer(String manufacturer) { - this.manufacturer = manufacturer; - } - - - public String getDesignation() { - return designation; - } - - - public void setDesignation(String designation) { - this.designation = designation; - } - - - public String getDigest() { - return digest; - } - - - public void setDigest(String digest) { - this.digest = digest; - } - - - public double getDiameter() { - return diameter; - } - - - public void setDiameter(double diameter) { - this.diameter = diameter; - } - - - public double getLength() { - return length; - } - - - public void setLength(double length) { - this.length = length; - } - - - public double getDelay() { - return delay; - } - - - public void setDelay(double delay) { - this.delay = delay; - } - - - @Override - public boolean replaceBy(Message other) { - return false; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - long temp; - temp = Double.doubleToLongBits(delay); - result = prime * result + (int) (temp ^ (temp >>> 32)); - result = prime * result - + ((designation == null) ? 0 : designation.hashCode()); - temp = Double.doubleToLongBits(diameter); - result = prime * result + (int) (temp ^ (temp >>> 32)); - result = prime * result - + ((digest == null) ? 0 : digest.hashCode()); - temp = Double.doubleToLongBits(length); - result = prime * result + (int) (temp ^ (temp >>> 32)); - result = prime * result - + ((manufacturer == null) ? 0 : manufacturer.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - MissingMotor other = (MissingMotor) obj; - if (Double.doubleToLongBits(delay) != Double - .doubleToLongBits(other.delay)) - return false; - if (designation == null) { - if (other.designation != null) - return false; - } else if (!designation.equals(other.designation)) - return false; - if (Double.doubleToLongBits(diameter) != Double - .doubleToLongBits(other.diameter)) - return false; - if (digest == null) { - if (other.digest != null) - return false; - } else if (!digest.equals(other.digest)) - return false; - if (Double.doubleToLongBits(length) != Double - .doubleToLongBits(other.length)) - return false; - if (manufacturer == null) { - if (other.manufacturer != null) - return false; - } else if (!manufacturer.equals(other.manufacturer)) - return false; - if (type != other.type) - return false; - return true; - } - - } - - - /** - * An unspecified warning type. This warning type holds a String - * describing it. Two warnings of this type are considered equal if the strings - * are identical. - * - * @author Sampo Niskanen - */ - public static class Other extends Warning { - private final String description; - - public Other(String description) { - this.description = description; - } - - @Override - public String toString() { - return description; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Other)) - return false; - - Other o = (Other) other; - return (o.description.equals(this.description)); - } - - @Override - public int hashCode() { - return description.hashCode(); - } - - @Override - public boolean replaceBy(Message other) { - return false; - } - } - - - /** A Warning that the body diameter is discontinuous. */ - ////Discontinuity in rocket body diameter. - public static final Warning DIAMETER_DISCONTINUITY = new Other(trans.get("Warning.DISCONTINUITY")); - - /** A Warning that a ComponentAssembly has an open forward end */ - public static final Warning OPEN_AIRFRAME_FORWARD = new Other(trans.get("Warning.OPEN_AIRFRAME_FORWARD")); - - /** A Warning that there is a gap in the airframe */ - public static final Warning AIRFRAME_GAP = new Other(trans.get("Warning.AIRFRAME_GAP")); - - /** A Warning that there are overlapping airframe components */ - public static final Warning AIRFRAME_OVERLAP = new Other(trans.get("Warning.AIRFRAME_OVERLAP")); - - /** A Warning that an inline podset is completely forward of its parent component */ - public static final Warning PODSET_FORWARD = new Other(trans.get("Warning.PODSET_FORWARD")); - - /** A Warning that an inline podset overlaps its parent component */ - public static final Warning PODSET_OVERLAP = new Other(trans.get("Warning.PODSET_OVERLAP")); - - /** A Warning that the fins are thick compared to the rocket body. */ - ////Thick fins may not be modeled accurately. - public static final Warning THICK_FIN = new Other(trans.get("Warning.THICK_FIN")); - - /** A Warning that the fins have jagged edges. */ - ////Jagged-edged fin predictions may be inaccurate. - public static final Warning JAGGED_EDGED_FIN = new Other(trans.get("Warning.JAGGED_EDGED_FIN")); - - /** A Warning that the fins have a zero area. */ - ////Fins with no area will not affect aerodynamics - public static final Warning ZERO_AREA_FIN = new Other(trans.get("Warning.ZERO_AREA_FIN")); - - /** A Warning that simulation listeners have affected the simulation */ - ////Listeners modified the flight simulation - public static final Warning LISTENERS_AFFECTED = new Other(trans.get("Warning.LISTENERS_AFFECTED")); - - ////Recovery device opened while motor still burning. - public static final Warning RECOVERY_DEPLOYMENT_WHILE_BURNING = new Other(trans.get("Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING")); - - ////No recovery device for simulation - public static final Warning NO_RECOVERY_DEVICE = new Other(trans.get("Warning.NO_RECOVERY_DEVICE")); - - //// Invalid parameter encountered, ignoring. - public static final Warning FILE_INVALID_PARAMETER = new Other(trans.get("Warning.FILE_INVALID_PARAMETER")); - - public static final Warning PARALLEL_FINS = new Other(trans.get("Warning.PARALLEL_FINS")); - - public static final Warning SUPERSONIC = new Other(trans.get("Warning.SUPERSONIC")); - - public static final Warning RECOVERY_LAUNCH_ROD = new Other(trans.get("Warning.RECOVERY_LAUNCH_ROD")); - - public static final Warning TUMBLE_UNDER_THRUST = new Other(trans.get("Warning.TUMBLE_UNDER_THRUST")); - - public static final Warning EVENT_AFTER_LANDING = new Other(trans.get("Warning.EVENT_AFTER_LANDING")); - - public static final Warning ZERO_VOLUME_BODY = new Other(trans.get("Warning.ZERO_VOLUME_BODY")); - - public static final Warning TUBE_SEPARATION = new Other(trans.get("Warning.TUBE_SEPARATION")); - public static final Warning TUBE_OVERLAP = new Other(trans.get("Warning.TUBE_OVERLAP")); - - public static final Warning SEPARATION_ORDER = new Other(trans.get("Warning.SEPARATION_ORDER")); - - public static final Warning EMPTY_BRANCH = new Other(trans.get("Warning.EMPTY_BRANCH")); -} diff --git a/core/src/net/sf/openrocket/logging/WarningSet.java b/core/src/net/sf/openrocket/logging/WarningSet.java deleted file mode 100644 index 2220904ba..000000000 --- a/core/src/net/sf/openrocket/logging/WarningSet.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.sf.openrocket.logging; - -import java.util.AbstractSet; -import java.util.Iterator; - -import net.sf.openrocket.util.ArrayList; -import net.sf.openrocket.util.BugException; -import net.sf.openrocket.util.Monitorable; -import net.sf.openrocket.util.Mutable; - -/** - * A set that contains multiple Warnings. When adding a - * {@link Warning} to this set, the contents is checked for a warning of the - * same type. If one is found, then the warning left in the set is determined - * by the method {@link Warning#replaceBy(Message)}. - *

- * A WarningSet can be made immutable by calling {@link #immute()}. - * - * @author Sampo Niskanen - */ -public class WarningSet extends MessageSet { - /** - * Add a Warning with the specified text to the set. The Warning object - * is created using the {@link Message#fromString(String)} method. If a warning of the - * same type exists in the set, the warning that is left in the set is defined by the - * method {@link Warning#replaceBy(Message)}. - * - * @param s the message text. - * @throws IllegalStateException if this message set has been made immutable. - */ - public boolean add(String s) { - mutable.check(); - return add(Warning.fromString(s)); - } - - @Override - public WarningSet clone() { - try { - WarningSet newSet = (WarningSet) super.clone(); - newSet.messages = this.messages.clone(); - newSet.mutable = this.mutable.clone(); - return newSet; - - } catch (CloneNotSupportedException e) { - throw new BugException("CloneNotSupportedException occurred, report bug!", e); - } - } -} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java deleted file mode 100644 index 335fe25b8..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.sf.openrocket.gui.dialogs; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.components.StyledLabel; -import net.sf.openrocket.gui.util.BetterListCellRenderer; -import net.sf.openrocket.logging.Error; -import net.sf.openrocket.logging.ErrorSet; -import net.sf.openrocket.logging.Warning; -import net.sf.openrocket.logging.WarningSet; - -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.ListSelectionModel; -import java.awt.Color; -import java.awt.Component; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -/** - * A message dialog displaying errors and warnings. - */ -@SuppressWarnings("serial") -public abstract class ErrorWarningDialog { - public static void showErrorsAndWarnings(Component parent, Object message, String title, ErrorSet errors, WarningSet warnings) { - JPanel content = new JPanel(new MigLayout("ins 0, fillx")); - - StyledLabel label = new StyledLabel("Errors"); - label.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); - content.add(label, "wrap, gaptop 15lp"); - - Error[] e = errors.toArray(new Error[0]); - final JList errorList = new JList<>(e); - errorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - errorList.setCellRenderer(new ErrorListCellRenderer()); - JScrollPane errorPane = new JScrollPane(errorList); - content.add(errorPane, "wrap, growx"); - - // Deselect items if clicked on blank region - errorList.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - int selectedIndex = errorList.locationToIndex(e.getPoint()); - if (selectedIndex < 0 || !errorList.getCellBounds(0, errorList.getLastVisibleIndex()).contains(e.getPoint())) { - errorList.clearSelection(); - } - } - }); - - content.add(new JSeparator(JSeparator.HORIZONTAL), "wrap"); - - content.add(new JLabel("Warnings:"), "wrap"); - - Warning[] w = warnings.toArray(new Warning[0]); - final JList warningList = new JList<>(w); - warningList.setCellRenderer(new BetterListCellRenderer()); - JScrollPane warningPane = new JScrollPane(warningList); - content.add(warningPane, "wrap, growx"); - - // Deselect items if clicked on blank region - warningList.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - int selectedIndex = warningList.locationToIndex(e.getPoint()); - if (selectedIndex < 0 || !warningList.getCellBounds(0, warningList.getLastVisibleIndex()).contains(e.getPoint())) { - warningList.clearSelection(); - } - } - }); - - JOptionPane.showMessageDialog(parent, new Object[] { message, content }, - title, JOptionPane.WARNING_MESSAGE); - - } - - private static class ErrorListCellRenderer extends BetterListCellRenderer { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - - // Text color - if (isSelected) { - label.setForeground(Color.WHITE); - } else { - label.setForeground(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); - } - - return label; - } - } -} diff --git a/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java b/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java deleted file mode 100644 index b8bbdd40f..000000000 --- a/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.sf.openrocket.gui.util; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.JLabel; -import javax.swing.JList; -import java.awt.Color; -import java.awt.Component; - -/** - * An improved list cell renderer, with alternating row background colors. - * - * @author Sibo Van Gool - */ -public class BetterListCellRenderer extends DefaultListCellRenderer { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - - // Alternating row colors - if (!isSelected) { - if (index % 2 == 0) { - label.setBackground(Color.WHITE); - } else { - label.setBackground(new Color(245, 245, 245)); - } - } - // Text color - if (isSelected) { - label.setForeground(Color.WHITE); - } else { - label.setForeground(Color.BLACK); - } - return label; - } -} From 13380886212a3204f45944736aca8366fea4b1d8 Mon Sep 17 00:00:00 2001 From: thzero Date: Sat, 6 May 2023 11:38:13 -0500 Subject: [PATCH 76/76] merge cleanup --- .../aerodynamics/BarrowmanCalculatorTest.java | 639 ------------------ 1 file changed, 639 deletions(-) delete mode 100644 core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java deleted file mode 100644 index eba208790..000000000 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ /dev/null @@ -1,639 +0,0 @@ -package net.sf.openrocket.aerodynamics; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import net.sf.openrocket.logging.WarningSet; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; - -import net.sf.openrocket.ServicesForTesting; -import net.sf.openrocket.plugin.PluginModule; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.NoseCone; -import net.sf.openrocket.rocketcomponent.ParallelStage; -import net.sf.openrocket.rocketcomponent.PodSet; -import net.sf.openrocket.rocketcomponent.RailButton; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.MathUtil; -import net.sf.openrocket.util.TestRockets; - -public class BarrowmanCalculatorTest { - protected final double EPSILON = 0.00001; - - private static Injector injector; - - @BeforeClass - public static void setup() { - Module applicationModule = new ServicesForTesting(); - Module pluginModule = new PluginModule(); - - injector = Guice.createInjector( applicationModule, pluginModule); - Application.setInjector(injector); - -// { -// GuiModule guiModule = new GuiModule(); -// Module pluginModule = new PluginModule(); -// Injector injector = Guice.createInjector(guiModule, pluginModule); -// Application.setInjector(injector); -// } - } - - /** - * Test a completely empty rocket. - */ - @Test - public void testEmptyRocket() { - // First test completely empty rocket - Rocket rocket = new Rocket(); - FlightConfiguration config = rocket.getSelectedConfiguration(); - BarrowmanCalculator calc = new BarrowmanCalculator(); - FlightConditions conditions = new FlightConditions(config); - WarningSet warnings = new WarningSet(); - - Coordinate cp_calc = calc.getCP(config, conditions, warnings); - - assertEquals(" Empty rocket CNa value is incorrect:", 0.0, cp_calc.weight , 0.0); - assertEquals(" Empty rocket cp x value is incorrect:", 0.0, cp_calc.x , 0.0); - assertEquals(" Empty rocket cp y value is incorrect:", 0.0, cp_calc.y , 0.0); - assertEquals(" Empty rocket cp z value is incorrect:", 0.0, cp_calc.z , 0.0); - } - - @Test - public void testCPSimpleDry() { - Rocket rocket = TestRockets.makeEstesAlphaIII(); - AxialStage stage = (AxialStage)rocket.getChild(0); - FlightConfiguration config = rocket.getSelectedConfiguration(); - BarrowmanCalculator calc = new BarrowmanCalculator(); - FlightConditions conditions = new FlightConditions(config); - WarningSet warnings = new WarningSet(); - - // By Hand: i.e. Manually calculate the Barrowman numbers - double exp_cna; - double exp_cpx; - { - NoseCone nose = (NoseCone)stage.getChild(0); - assertEquals(" Estes Alpha III nose cone has incorrect length:", 0.07, nose.getLength(), EPSILON); - assertEquals(" Estes Alpha III nosecone has wrong (base) radius:", 0.012, nose.getAftRadius(), EPSILON); - assertEquals(" Estes Alpha III nosecone has wrong type:", Transition.Shape.OGIVE, nose.getShapeType()); - double cna_nose = 2; - double cpx_nose = 0.03235; - - double cna_body=0; // equal-to-zero, see [Barrowman66] p15. - double cpx_body=0; - - double cna_3fin = 24.146933; - double cpx_3fin = 0.0193484; - double fin_x = 0.22; - cpx_3fin += fin_x; - - double cna_lugs=0; // n/a - double cpx_lugs=0; // n/a - - // N.B. CP @ AoA = zero - exp_cna = cna_nose + cna_body + cna_3fin + cna_lugs; - exp_cpx = ( cna_nose*cpx_nose + cna_body*cpx_body + cna_3fin*cpx_3fin + cna_lugs*cpx_lugs)/exp_cna; - } - - Coordinate cp_calc = calc.getCP(config, conditions, warnings); - - assertEquals(" Estes Alpha III CNa value is incorrect:", exp_cna, cp_calc.weight, EPSILON); - assertEquals(" Estes Alpha III cp x value is incorrect:", exp_cpx, cp_calc.x, EPSILON); - assertEquals(" Estes Alpha III cp y value is incorrect:", 0.0, cp_calc.y, EPSILON); - } - - @Test - public void testCPSimpleWithMotor() { - Rocket rkt = TestRockets.makeEstesAlphaIII(); - FlightConfiguration config = rkt.getSelectedConfiguration(); - AerodynamicCalculator calc = new BarrowmanCalculator(); - FlightConditions conditions = new FlightConditions(config); - WarningSet warnings = new WarningSet(); - - // calculated from OpenRocket 15.03: - //double expCPx = 0.225; - // verified from the equations: - double expCPx = 0.2235154; - double exp_cna = 26.146933; - Coordinate calcCP = calc.getCP(config, conditions, warnings); - - assertEquals(" Estes Alpha III cp x value is incorrect:", expCPx, calcCP.x, EPSILON); - assertEquals(" Estes Alpha III CNa value is incorrect:", exp_cna, calcCP.weight, EPSILON); - } - - // Component CP calculations resulting in expected test values are in comments in TestRockets.makeFalcon9Heavy() - @Test - public void testCPParallelBoosters() { - final Rocket rocket = TestRockets.makeFalcon9Heavy(); - final ParallelStage boosterStage = (ParallelStage) rocket.getChild(1).getChild(0).getChild(0); - final TrapezoidFinSet boosterFins = (TrapezoidFinSet) boosterStage.getChild(1).getChild(1); - final FlightConfiguration config = rocket.getSelectedConfiguration(); - final BarrowmanCalculator calc = new BarrowmanCalculator(); - final FlightConditions conditions = new FlightConditions(config); - final WarningSet warnings = new WarningSet(); - - { - boosterFins.setFinCount(3); - final Coordinate cp_3fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 16.51651439, cp_3fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 1.00667319, cp_3fin.x, EPSILON); - assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0.0, cp_3fin.y, EPSILON); - assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0.0, cp_3fin.z, EPSILON); - }{ - boosterFins.setFinCount(2); - boosterFins.setAngleOffset(Math.PI/4); - final Coordinate cp_2fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 12.1073483560, cp_2fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.9440139181, cp_2fin.x, EPSILON); - assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0.0, cp_2fin.y, EPSILON); - assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0.0, cp_2fin.z, EPSILON); - }{ - boosterFins.setFinCount(1); - final Coordinate cp_1fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 7.6981823141, cp_1fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.8095779106, cp_1fin.x, EPSILON); - assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0f, cp_1fin.y, EPSILON); - assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0f, cp_1fin.z, EPSILON); - } - } - - @Test - public void testFinCountEffect() { - final BarrowmanCalculator calc = new BarrowmanCalculator(); - final WarningSet warnings = new WarningSet(); - - final Rocket rocket = TestRockets.makeEstesAlphaIII(); - final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConditions conditions = new FlightConditions(config); - { - ((FinSet)rocket.getChild(0).getChild(1).getChild(0)).setFinCount(4); - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 34.19591165, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22724216, wholeRocketCP.x, EPSILON); - }{ - ((FinSet)rocket.getChild(0).getChild(1).getChild(0)).setFinCount(3); - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 26.14693374, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22351541, wholeRocketCP.x, EPSILON); - }{ - ((FinSet)rocket.getChild(0).getChild(1).getChild(0)).setFinCount(2); - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 2.0, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.032356, wholeRocketCP.x, EPSILON); - }{ - ((FinSet)rocket.getChild(0).getChild(1).getChild(0)).setFinCount(1); - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 2.0, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.032356, wholeRocketCP.x, EPSILON); - } - } - - @Test - public void testCpSplitTripleFin() { - final BarrowmanCalculator calc = new BarrowmanCalculator(); - final WarningSet warnings = new WarningSet(); - - final Rocket rocket = TestRockets.makeEstesAlphaIII(); - final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConditions conditions = new FlightConditions(config); - - { - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 26.14693374, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22351541, wholeRocketCP.x, EPSILON); - }{ - final BodyTube body = (BodyTube)rocket.getChild(0).getChild(1); - final TrapezoidFinSet fins = (TrapezoidFinSet)body.getChild(0); - fins.setAngleOffset(0); - TestRockets.splitRocketFins(body, fins, 3); - - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 26.14693374, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22351541, wholeRocketCP.x, EPSILON); - } - } - - @Test - public void testCpSplitQuadrupleFin() { - final BarrowmanCalculator calc = new BarrowmanCalculator(); - final WarningSet warnings = new WarningSet(); - - final Rocket rocket = TestRockets.makeEstesAlphaIII(); - final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConditions conditions = new FlightConditions(config); - - { - ((FinSet)rocket.getChild(0).getChild(1).getChild(0)).setFinCount(4); - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 34.19591165, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22724, wholeRocketCP.x, EPSILON); - }{ - final BodyTube body = (BodyTube)rocket.getChild(0).getChild(1); - final TrapezoidFinSet fins = (TrapezoidFinSet)body.getChild(0); - TestRockets.splitRocketFins(body, fins, 4); - - final Coordinate wholeRocketCP = calc.getCP(config, conditions, warnings); - assertEquals("Split-Fin Rocket CNa value is incorrect:", 34.19591165, wholeRocketCP.weight, EPSILON); - assertEquals("Split-Fin Rocket CP x value is incorrect:", 0.22724, wholeRocketCP.x, EPSILON); - } - } - // test rocket with endplates on fins. Comments tracing - // calculation of CP are in TestRockets.makeEndPlateRocket(). - @Test - public void testEndPlateCP() { - final Rocket rocket = TestRockets.makeEndPlateRocket(); - final FlightConfiguration config = new FlightConfiguration(rocket, null); - // rocket.setFlightConfiguration(config.getId(), config); - // rocket.setSelectedConfiguration(config.getId()); - final AerodynamicCalculator calc = new BarrowmanCalculator(); - final FlightConditions conditions = new FlightConditions(config); - final WarningSet warnings = new WarningSet(); - - final Coordinate cp = calc.getCP(config, conditions, warnings); - assertEquals(" Endplate rocket cp x value is incorrect:", 0.25461, cp.x, EPSILON); - assertEquals(" Endplate rocket cp y value is incorrect:", 0.0, cp.y, EPSILON); - assertEquals(" Endplate rocket cp z value is incorrect:", 0.0, cp.z, EPSILON); - assertEquals(" Endplate rocket CNa value is incorrect:", 40.96857, cp.weight, EPSILON); - } - - @Test - public void testGetWorstCP() { -// Rocket rocket = TestRockets.makeFalcon9Heavy(); -// FlightConfiguration config = rocket.getSelectedConfiguration(); -// BarrowmanCalculator calc = new BarrowmanCalculator(); -// FlightConditions conditions = new FlightConditions(config); -// WarningSet warnings = new WarningSet(); - - // NYI -// Coordinate calcBestCP = calc.getCP(config, conditions, warnings); -// Coordinate calcWorstCP = calc.getWorstCP(config, conditions, warnings); - - //fail("Not yet implemented"); -// Coordinate expBestCP = new Coordinate( -1, 0,0,0); -// assertEquals(" Falcon Heavy best CP x value is incorrect:", expBestCP.x, calcBestCP.x, EPSILON); -// Coordinate expWorstCP = new Coordinate( -1, 0,0,0); -// assertEquals(" Falcon Heavy Worst CP x value is incorrect:", expWorstCP.x, calcWorstCP.x, EPSILON); - } - - @Test - public void testContinuousRocket() { - Rocket rocket = TestRockets.makeEstesAlphaIII(); - AerodynamicCalculator calc = new BarrowmanCalculator(); - FlightConfiguration configuration = rocket.getSelectedConfiguration(); - WarningSet warnings = new WarningSet(); - - calc.checkGeometry(configuration, rocket, warnings); - assertTrue("Estes Alpha III should be continuous: ", warnings.isEmpty()); - } - - @Test - public void testContinuousRocketWithStrapOns() { - Rocket rocket = TestRockets.makeFalcon9Heavy(); - AerodynamicCalculator calc = new BarrowmanCalculator(); - FlightConfiguration configuration = rocket.getSelectedConfiguration(); - WarningSet warnings = new WarningSet(); - - calc.checkGeometry(configuration, rocket, warnings); - assertTrue("F9H should be continuous: ", warnings.isEmpty()); - } - - @Test - public void testRadialDiscontinuousRocket() { - Rocket rocket = TestRockets.makeEstesAlphaIII(); - AerodynamicCalculator calc = new BarrowmanCalculator(); - FlightConfiguration configuration = rocket.getSelectedConfiguration(); - WarningSet warnings = new WarningSet(); - - NoseCone nose = (NoseCone)rocket.getChild(0).getChild(0); - BodyTube body = (BodyTube)rocket.getChild(0).getChild(1); - - nose.setAftRadius(0.015); - body.setOuterRadius( 0.012 ); - body.setName( body.getName()+" << discontinuous"); - - calc.checkGeometry(configuration, rocket, warnings); - assertFalse(" Estes Alpha III has an undetected discontinuity:", warnings.isEmpty()); - } - - @Test - public void testRadialDiscontinuityWithStrapOns() { - Rocket rocket = TestRockets.makeFalcon9Heavy(); - AerodynamicCalculator calc = new BarrowmanCalculator(); - FlightConfiguration configuration = rocket.getSelectedConfiguration(); - WarningSet warnings = new WarningSet(); - - final AxialStage coreStage = (AxialStage)rocket.getChild(1); - final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0); - - NoseCone nose = (NoseCone)booster.getChild(0); - BodyTube body = (BodyTube)booster.getChild(1); - - nose.setAftRadius(0.015); - body.setOuterRadius( 0.012 ); - body.setName( body.getName()+" << discontinuous"); - - calc.checkGeometry(configuration, rocket, warnings); - assertFalse(" Missed discontinuity in Falcon 9 Heavy:" , warnings.isEmpty()); - } - - @Test - public void testPhantomTubes() { - Rocket rocketNoPods = TestRockets.makeEstesAlphaIII(); - FlightConfiguration configNoPods = rocketNoPods.getSelectedConfiguration(); - FlightConditions conditionsNoPods = new FlightConditions(configNoPods); - WarningSet warningsNoPods = new WarningSet(); - - Rocket rocketWithPods = TestRockets.makeEstesAlphaIIIWithPods(); - FlightConfiguration configPods = rocketWithPods.getSelectedConfiguration(); - FlightConditions conditionsPods = new FlightConditions(configPods); - WarningSet warningsPods = new WarningSet(); - AerodynamicCalculator calcPods = new BarrowmanCalculator(); - AerodynamicCalculator calcNoPods = new BarrowmanCalculator(); - - final AerodynamicForces forcesNoPods = calcPods.getAerodynamicForces(configNoPods, conditionsNoPods, warningsNoPods); - final AerodynamicForces forcesPods = calcPods.getAerodynamicForces(configPods, conditionsPods, warningsPods); - assertEquals(" Estes Alpha III With Pods rocket CD value is incorrect:", forcesPods.getCD(), forcesNoPods.getCD(), EPSILON); - - // The "with pods" version has no way of seeing the fins are - // on the actual body tube rather than the phantom tubes, - // so CD won't take fin-body interference into consideration. - // So we'll adjust our CD in these tests. The magic numbers - // in x and w come from temporarily disabling the - // interference calculation in FinSetCalc and comparing - // results with and without it - // cpNoPods (0.34125,0.00000,0.00000,w=16.20502) -- interference disabled - // cpNoPods (0.34797,0.00000,0.00000,w=19.34773) -- interference enabled - - final Coordinate cpNoPods = calcNoPods.getCP(configNoPods, conditionsNoPods, warningsNoPods); - final Coordinate cpPods = calcPods.getCP(configPods, conditionsPods, warningsPods); - assertEquals(" Alpha III With Pods rocket cp x value is incorrect:", cpNoPods.x - 0.002788761352, cpPods.x, EPSILON); - assertEquals(" Alpha III With Pods rocket cp y value is incorrect:", cpNoPods.y, cpPods.y, EPSILON); - assertEquals(" Alpha III With Pods rocket cp z value is incorrect:", cpNoPods.z, cpPods.z, EPSILON); - assertEquals(" Alpha III With Pods rocket CNa value is incorrect:", cpPods.weight, cpNoPods.weight - 3.91572, EPSILON); - } - - /** - * Tests whether adding extra empty stages has an effect. - */ - @Test - public void testEmptyStages() { - // Reference rocket - Rocket rocketRef = TestRockets.makeEstesAlphaIII(); - FlightConfiguration configRef = rocketRef.getSelectedConfiguration(); - BarrowmanCalculator calcRef = new BarrowmanCalculator(); - FlightConditions conditionsRef = new FlightConditions(configRef); - WarningSet warnings = new WarningSet(); - - Coordinate cp_calcRef = calcRef.getCP(configRef, conditionsRef, warnings); - - // First test with adding an empty stage in the front of the design - Rocket rocketFront = TestRockets.makeEstesAlphaIII(); - AxialStage stage1 = new AxialStage(); // To be placed in front of the design - rocketFront.addChild(stage1, 0); - FlightConfiguration configFront = rocketFront.getSelectedConfiguration(); - BarrowmanCalculator calcFront = new BarrowmanCalculator(); - FlightConditions conditionsFront = new FlightConditions(configFront); - warnings = new WarningSet(); - - Coordinate cp_calcFront = calcFront.getCP(configFront, conditionsFront, warnings); - - assertEquals(" Estes Alpha III with front empty stage CNa value is incorrect:", cp_calcRef.weight, cp_calcFront.weight , EPSILON); - assertEquals(" Estes Alpha III with front empty stage cp x value is incorrect:", cp_calcRef.x, cp_calcFront.x , EPSILON); - assertEquals(" Estes Alpha III with front empty stage cp y value is incorrect:", cp_calcRef.y, cp_calcFront.y , EPSILON); - assertEquals(" Estes Alpha III with front empty stage cp z value is incorrect:", cp_calcRef.z, cp_calcFront.z , EPSILON); - - // Now test with adding an empty stage in the rear of the design - Rocket rocketRear = TestRockets.makeEstesAlphaIII(); - AxialStage stage2 = new AxialStage(); // To be placed in the rear of the design - rocketRear.addChild(stage2); - FlightConfiguration configRear = rocketRear.getSelectedConfiguration(); - BarrowmanCalculator calcRear = new BarrowmanCalculator(); - FlightConditions conditionsRear = new FlightConditions(configRear); - warnings = new WarningSet(); - - Coordinate cp_calcRear = calcRear.getCP(configRear, conditionsRear, warnings); - - assertEquals(" Estes Alpha III with rear empty stage CNa value is incorrect:", cp_calcRef.weight, cp_calcRear.weight , EPSILON); - assertEquals(" Estes Alpha III with rear empty stage cp x value is incorrect:", cp_calcRef.x, cp_calcRear.x , EPSILON); - assertEquals(" Estes Alpha III with rear empty stage cp y value is incorrect:", cp_calcRef.y, cp_calcRear.y , EPSILON); - assertEquals(" Estes Alpha III with rear empty stage cp z value is incorrect:", cp_calcRef.z, cp_calcRear.z , EPSILON); - - // Test with multiple empty stages - Rocket rocketMulti = rocketFront; - AxialStage stage3 = new AxialStage(); // To be placed in the rear of the design - rocketMulti.addChild(stage3); - FlightConfiguration configMulti = rocketMulti.getSelectedConfiguration(); - BarrowmanCalculator calcMulti = new BarrowmanCalculator(); - FlightConditions conditionsMulti = new FlightConditions(configMulti); - warnings = new WarningSet(); - - Coordinate cp_calcMulti = calcMulti.getCP(configMulti, conditionsMulti, warnings); - - assertEquals(" Estes Alpha III with multiple empty stages CNa value is incorrect:", cp_calcRef.weight, cp_calcMulti.weight , EPSILON); - assertEquals(" Estes Alpha III with multiple empty stages cp x value is incorrect:", cp_calcRef.x, cp_calcMulti.x , EPSILON); - assertEquals(" Estes Alpha III with multiple empty stages cp y value is incorrect:", cp_calcRef.y, cp_calcMulti.y , EPSILON); - assertEquals(" Estes Alpha III with multiple empty stages cp z value is incorrect:", cp_calcRef.z, cp_calcMulti.z , EPSILON); - } - - /** - * Tests in-line pod aerodynamics and warnings - * - */ - @Test - public void testInlinePods() { - WarningSet warnings = new WarningSet(); - - // reference rocket and results - final Rocket refRocket = TestRockets.makeEstesAlphaIII(); - final FlightConfiguration refConfig = refRocket.getSelectedConfiguration(); - final FlightConditions refConditions = new FlightConditions(refConfig); - - final BarrowmanCalculator refCalc = new BarrowmanCalculator(); - double refCP = refCalc.getCP(refConfig, refConditions, warnings).x; - final AerodynamicForces refForces = refCalc.getAerodynamicForces(refConfig, refConditions, warnings); - assertTrue("reference rocket should have no warnings", warnings.isEmpty()); - final double refCD = refForces.getCD(); - - // test rocket - final Rocket testRocket = TestRockets.makeEstesAlphaIIIwithInlinePod(); - final PodSet pod = (PodSet) testRocket.getChild(0).getChild(1).getChild(0); - final FlightConfiguration testConfig = testRocket.getSelectedConfiguration(); - final FlightConditions testConditions = new FlightConditions(testConfig); - - final BarrowmanCalculator testCalc = new BarrowmanCalculator(); - double testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - final AerodynamicForces testForces = testCalc.getAerodynamicForces(testConfig, testConditions, warnings); - assertTrue("test rocket should have no warnings", warnings.isEmpty()); - - assertEquals("ref and test rocket CP should match", refCP, testCP, EPSILON); - - final double testCD = testForces.getCD(); - assertEquals("ref and test rocket CD should match", refCD, testCD, EPSILON); - - // move the pod back. - pod.setAxialOffset(pod.getAxialOffset() + 0.1); - testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - assertEquals("should be warning from gap in airframe", 1, warnings.size()); - - // move the pod forward. - warnings.clear(); - pod.setAxialOffset(pod.getAxialOffset() - 0.3); - testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - assertEquals("should be warning from airframe overlap", 1, warnings.size()); - - // move the pod back. - warnings.clear(); - pod.setAxialOffset(pod.getAxialOffset() + 0.1); - testCP = testCalc.getCP(testConfig, testConditions, warnings).x; - assertEquals("should be warning from podset airframe overlap", 1, warnings.size()); - } - - @Test - public void testBaseDragWithOverride() { - final WarningSet warnings = new WarningSet(); - final BarrowmanCalculator calc = new BarrowmanCalculator(); - - // get base drag of minimal rocket consisting of just a tube. - final Rocket tubeRocket = new Rocket(); - final AxialStage tubeStage = new AxialStage(); - tubeRocket.addChild(tubeStage); - - final BodyTube tubeBodyTube = new BodyTube(); - tubeStage.addChild(tubeBodyTube); - - final FlightConfiguration tubeConfig = new FlightConfiguration(tubeRocket); - final FlightConditions tubeConditions = new FlightConditions(tubeConfig); - final AerodynamicForces tubeForces = calc.getAerodynamicForces(tubeConfig, tubeConditions, warnings); - final double tubeBaseCD = tubeForces.getBaseCD(); - - // get base CD of minimal rocket consisting of just a cone - final Rocket coneRocket = new Rocket(); - final AxialStage coneStage = new AxialStage(); - coneRocket.addChild(coneStage); - - NoseCone coneCone = new NoseCone(); - coneCone.setAftRadius(tubeBodyTube.getOuterRadius()); - coneStage.addChild(coneCone); - - final FlightConfiguration coneConfig = new FlightConfiguration(coneRocket); - final FlightConditions coneConditions = new FlightConditions(coneConfig); - final AerodynamicForces coneForces = calc.getAerodynamicForces(coneConfig, coneConditions, warnings); - final double coneBaseCD = coneForces.getBaseCD(); - - // now our test rocket, with a tube and a cone - final Rocket testRocket = new Rocket(); - final AxialStage testStage = new AxialStage(); - testRocket.addChild(testStage); - - final BodyTube testTube = new BodyTube(); - testTube.setOuterRadius(tubeBodyTube.getOuterRadius()); - testStage.addChild(testTube); - - final NoseCone testCone = new NoseCone(); - testCone.setAftRadius(coneCone.getAftRadius()); - testStage.addChild(testCone); - - FlightConfiguration testConfig = new FlightConfiguration(testRocket); - FlightConditions testConditions = new FlightConditions(testConfig); - - // no overrides - AerodynamicForces testForces = calc.getAerodynamicForces(testConfig, testConditions, warnings); - assertEquals("base CD should be base CD of tube plus base CD of cone", tubeBaseCD + coneBaseCD, testForces.getBaseCD(), EPSILON); - - // override tube CD - testTube.setCDOverridden(true); - testTube.setOverrideCD(0); - testForces = calc.getAerodynamicForces(testConfig, testConditions, warnings); - assertEquals("base CD should be base CD of cone", coneBaseCD, testForces.getBaseCD(), EPSILON); - - // override cone CD - testCone.setCDOverridden(true); - testCone.setOverrideCD(0); - testForces = calc.getAerodynamicForces(testConfig, testConditions, warnings); - assertEquals("base CD should be 0", 0.0, testForces.getBaseCD(), EPSILON); - - - // and turn off tube override - testTube.setCDOverridden(false); - testForces = calc.getAerodynamicForces(testConfig, testConditions, warnings); - assertEquals("base CD should be base CD of tube", tubeBaseCD, testForces.getBaseCD(), EPSILON); - } - - /** - * Tests railbutton drag. Really is testing instancing more than actual drag calculations, and making - * sure we don't divide by 0 when not moving - */ - @Test - public void testRailButtonDrag() { - // minimal rocket with nothing on it but two railbuttons - final Rocket rocket = new Rocket(); - - final AxialStage stage = new AxialStage(); - rocket.addChild(stage); - - // phantom tubes have no drag to confuse things - final BodyTube phantom = new BodyTube(); - phantom.setOuterRadius(0); - stage.addChild(phantom); - - // set up test environment - WarningSet warnings = new WarningSet(); - final FlightConfiguration config = rocket.getSelectedConfiguration(); - final FlightConditions conditions = new FlightConditions(config); - final BarrowmanCalculator calc = new BarrowmanCalculator(); - - // part 1: instancing - - // Put two individual railbuttons and get their CD - final RailButton button1 = new RailButton(); - button1.setInstanceCount(1); - button1.setAxialOffset(1.0); - phantom.addChild(button1); - - final RailButton button2 = new RailButton(); - button2.setInstanceCount(1); - button2.setAxialOffset(2.0); - phantom.addChild(button2); - - final AerodynamicForces individualForces = calc.getAerodynamicForces(config, conditions, warnings); - final double individualCD = individualForces.getCD(); - - // get rid of individual buttons and put in a railbutton set with two instances at same locations as original - // railbuttons - phantom.removeChild(button1); - phantom.removeChild(button2); - - final RailButton buttons = new RailButton(); - buttons.setInstanceCount(2); - buttons.setAxialOffset(1.0); - buttons.setInstanceSeparation(1.0); - - final AerodynamicForces pairForces = calc.getAerodynamicForces(config, conditions, warnings); - final double pairCD = pairForces.getCD(); - - assertEquals("two individual railbuttons should have same CD as a pair", individualCD, pairCD, EPSILON); - - // part 2: test at Mach 0 - conditions.setMach(MathUtil.EPSILON); - final AerodynamicForces epsForces = calc.getAerodynamicForces(config, conditions, warnings); - final double epsCD = epsForces.getCD(); - - conditions.setMach(0); - final AerodynamicForces zeroForces = calc.getAerodynamicForces(config, conditions, warnings); - final double zeroCD = zeroForces.getCD(); - assertEquals("drag at mach 0 should equal drag at mach MathUtil.EPSILON", epsCD, zeroCD, EPSILON); - } -} -