Implement simulation importing from RASAero

This commit is contained in:
SiboVG 2023-02-13 04:41:57 +00:00
parent 7a72a30b46
commit 357cae2947
9 changed files with 319 additions and 32 deletions

View File

@ -225,7 +225,14 @@ public class Simulation implements ChangeSource, Cloneable {
this.configId = fcid;
fireChangeEvent();
}
/**
* Applies the simulation options to the simulation.
* @param options the simulation options to apply.
*/
public void copySimulationOptionsFrom(SimulationOptions options) {
this.options.copyConditionsFrom(options);
}
// /**
// * Return a newly created Configuration for this simulation. The configuration

View File

@ -23,7 +23,7 @@ public class BoosterHandler extends BodyTubeHandler {
private double boatTailLength;
private double boatTailRearDiameter;
public BoosterHandler(DocumentLoadingContext context, RocketComponent parent, WarningSet warnings) {
public BoosterHandler(DocumentLoadingContext context, RocketComponent parent) {
super(context);
if (parent == null) {
throw new IllegalArgumentException("The parent component of a body tube may not be null.");

View File

@ -22,13 +22,12 @@ import java.util.HashMap;
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class FinCanHandler extends BodyTubeHandler {
private final BodyTube parentBodyTube;
private final PodSet finCan = new PodSet();
private double insideDiameter;
private double shoulderLength;
public FinCanHandler(DocumentLoadingContext context, RocketComponent parent, WarningSet warnings) {
public FinCanHandler(DocumentLoadingContext context, RocketComponent parent) {
super(context);
if (parent == null) {
throw new IllegalArgumentException("The parent component of a body tube may not be null.");
@ -43,8 +42,8 @@ public class FinCanHandler extends BodyTubeHandler {
}
// The fin can is a pod set child of the parent body tube.
this.parentBodyTube = (BodyTube) lastChild;
this.parentBodyTube.addChild(this.finCan);
BodyTube parentBodyTube = (BodyTube) lastChild;
parentBodyTube.addChild(this.finCan);
this.finCan.setInstanceCount(1);
this.finCan.setRadius(RadiusMethod.FREE, 0);
this.finCan.addChild(this.bodyTube);

View File

@ -1,8 +1,6 @@
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.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
@ -18,15 +16,10 @@ import java.util.HashMap;
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class LaunchSiteHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private final SimulationOptions simulationOptions;
private final SimulationOptions launchSiteSettings;
public LaunchSiteHandler(DocumentLoadingContext context) {
this.context = context;
Simulation simulation = new Simulation(context.getOpenRocketDocument().getRocket());
simulation.setName("RASAero II Launch Site");
this.simulationOptions = simulation.getOptions();
this.context.getOpenRocketDocument().addSimulation(simulation);
public LaunchSiteHandler(final SimulationOptions launchSiteSettings) {
this.launchSiteSettings = launchSiteSettings;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
@ -43,18 +36,18 @@ public class LaunchSiteHandler extends AbstractElementHandler {
public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
try {
if (RASAeroCommonConstants.LAUNCH_ALTITUDE.equals(element)) {
simulationOptions.setLaunchAltitude(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE);
launchSiteSettings.setLaunchAltitude(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE);
} else if (RASAeroCommonConstants.LAUNCH_PRESSURE.equals(element)) {
simulationOptions.setLaunchPressure(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_PRESSURE);
launchSiteSettings.setLaunchPressure(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_PRESSURE);
} else if (RASAeroCommonConstants.LAUNCH_ROD_ANGLE.equals(element)) {
simulationOptions.setLaunchRodAngle(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ANGLE);
launchSiteSettings.setLaunchRodAngle(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ANGLE);
} else if (RASAeroCommonConstants.LAUNCH_ROD_LENGTH.equals(element)) {
simulationOptions.setLaunchRodLength(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE);
launchSiteSettings.setLaunchRodLength(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_ALTITUDE);
} else if (RASAeroCommonConstants.LAUNCH_TEMPERATURE.equals(element)) {
simulationOptions.setLaunchTemperature(
launchSiteSettings.setLaunchTemperature(
RASAeroCommonConstants.RASAERO_TO_OPENROCKET_TEMPERATURE(Double.parseDouble(content)));
} else if (RASAeroCommonConstants.LAUNCH_WIND_SPEED.equals(element)) {
simulationOptions.setWindSpeedAverage(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SPEED);
launchSiteSettings.setWindSpeedAverage(Double.parseDouble(content) / RASAeroCommonConstants.RASAERO_TO_OPENROCKET_SPEED);
}
} catch (NumberFormatException e) {
warnings.add("Invalid number format for element " + element + ", ignoring.");

View File

@ -39,7 +39,7 @@ public class RASAeroCommonConstants {
// 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<String, Transition.Shape> RASAeroNoseConeShapeMap = new HashMap<>();
// Transition settings
@ -107,6 +107,21 @@ public class RASAeroCommonConstants {
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";
// TODO: SustainerLaunchWt, 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 INCLUDE_BOOSTER1 = "IncludeBooster1";
public static final String BOOSTER2_ENGINE = "Booster2Engine";
public static final String BOOSTER2_SEPARATION_DELAY = "Booster2SeparationDelay"; // Delay after booster burnout to separate
public static final String BOOSTER2_IGNITION_DELAY = "Booster2IgnitionDelay";
public static final String INCLUDE_BOOSTER2 = "IncludeBooster2";
/**
* Length conversion. RASAero is in inches, OpenRocket in meters.

View File

@ -9,6 +9,7 @@ import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.SimulationOptions;
import org.xml.sax.SAXException;
import java.util.HashMap;
@ -64,7 +65,7 @@ public class RASAeroHandler extends AbstractElementHandler {
/**
* A SAX handler for the RASAeroDocument element.
*/
private class RocketDocumentHandler extends AbstractElementHandler {
private static class RocketDocumentHandler extends AbstractElementHandler {
/**
* The DocumentLoadingContext
*/
@ -75,6 +76,11 @@ public class RASAeroHandler extends AbstractElementHandler {
*/
private final Rocket rocket;
/**
* The RASAero launch site settings to be used for all the OpenRocket simulations.
*/
private final SimulationOptions launchSiteSettings = new SimulationOptions();
/**
* The RASAero file version.
*/
@ -102,11 +108,15 @@ public class RASAeroHandler extends AbstractElementHandler {
}
// LaunchSite
else if (RASAeroCommonConstants.LAUNCH_SITE.equals(element)) {
return new LaunchSiteHandler(context);
return new LaunchSiteHandler(launchSiteSettings);
}
// Recovery
else if (RASAeroCommonConstants.RECOVERY.equals(element)) {
return new RecoveryHandler(context, rocket);
return new RecoveryHandler(rocket);
}
// SimulationList
else if (RASAeroCommonConstants.SIMULATION_LIST.equals(element)) {
return new SimulationListHandler(context, rocket, launchSiteSettings);
}
return null;
@ -128,7 +138,7 @@ public class RASAeroHandler extends AbstractElementHandler {
/**
* A SAX handler for the RocketDesign element.
*/
private class RocketDesignHandler extends AbstractElementHandler {
private static class RocketDesignHandler extends AbstractElementHandler {
/**
* The DocumentLoadingContext
*/
@ -161,11 +171,11 @@ public class RASAeroHandler extends AbstractElementHandler {
}
// Fin can
else if (RASAeroCommonConstants.FIN_CAN.equals(element)) {
return new FinCanHandler(context, component, warnings);
return new FinCanHandler(context, component);
}
// Booster
else if (RASAeroCommonConstants.BOOSTER.equals(element)) {
return new BoosterHandler(context, component, warnings);
return new BoosterHandler(context, component);
}
// BoatTail
else if (RASAeroCommonConstants.BOATTAIL.equals(element)) {

View File

@ -0,0 +1,95 @@
package net.sf.openrocket.file.rasaero.importt;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.database.motor.ThrustCurveMotorSet;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.startup.Application;
import java.util.ArrayList;
import java.util.List;
public abstract class RASAeroMotorsLoader {
private static List<ThrustCurveMotor> 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();
}
/*
RASAero file motor strings are formatted as "<motorName> (<manufacturer>)"
*/
String[] split = motorString.split("\\s{2}");
if (split.length != 2) {
return null;
}
String motorName = 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, 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.
* @throws RuntimeException If the RASAero motors file could not be found.
*
private static void loadAllRASAeroMotors(WarningSet warnings) throws RuntimeException {
allMotors = 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<ThrustCurveMotor.Builder> motors = loader.load(is, fileName);
for (ThrustCurveMotor.Builder builder : motors) {
allMotors.add(builder.build());
}
} catch (IOException e) {
warnings.add("Error during motor loading: " + e.getMessage());
}
}*/
/**
* Loads the OpenRocket motors database.
*/
private static void loadAllMotors() {
allMotors = new ArrayList<>();
List<ThrustCurveMotorSet> database = Application.getThrustCurveMotorSetDatabase().getMotorSets();
for (ThrustCurveMotorSet set : database) {
allMotors.addAll(set.getMotors());
}
}
}

View File

@ -25,7 +25,6 @@ import java.util.Map;
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class RecoveryHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private final Rocket rocket;
// Recovery parameters
@ -37,8 +36,7 @@ public class RecoveryHandler extends AbstractElementHandler {
private final String[] eventType = new String[NR_OF_RECOVERY_DEVICES]; // When to deploy
private final Double[] CD = new Double[NR_OF_RECOVERY_DEVICES]; // Coefficient of drag
public RecoveryHandler(DocumentLoadingContext context, Rocket rocket) {
this.context = context;
public RecoveryHandler(Rocket rocket) {
this.rocket = rocket;
}
@Override

View File

@ -0,0 +1,170 @@
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.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.SimulationOptions;
import org.xml.sax.SAXException;
import java.util.HashMap;
/**
* A SAX handler for simulation importing from a RASAero file.
* A SimulationList is a collection of RASAero simulations.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class SimulationListHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private final Rocket rocket;
private final SimulationOptions launchSiteSettings;
private int nrOfSimulations = 0;
public SimulationListHandler(DocumentLoadingContext context, Rocket rocket, SimulationOptions launchSiteSettings) {
this.context = context;
this.rocket = rocket;
this.launchSiteSettings = launchSiteSettings;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
if (RASAeroCommonConstants.SIMULATION.equals(element)) {
nrOfSimulations++;
return new SimulationHandler(context, rocket, launchSiteSettings, nrOfSimulations);
}
return null;
}
@Override
public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
RASAeroMotorsLoader.clearAllMotors();
}
/**
* Handles RASAero simulation elements.
* We will only import the motor information from it.
*/
private static class SimulationHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private final Rocket rocket;
private final SimulationOptions launchSiteSettings;
private final int simulationNr;
// Motor information
private ThrustCurveMotor sustainerEngine;
private Double sustainerIgnitionDelay;
private ThrustCurveMotor booster1Engine;
private Double booster1IgnitionDelay;
private Double booster1SeparationDelay;
private Boolean includeBooster1;
private ThrustCurveMotor booster2Engine;
private Double booster2IgnitionDelay;
private Double booster2SeparationDelay;
private Boolean includeBooster2;
public SimulationHandler(DocumentLoadingContext context, Rocket rocket, SimulationOptions launchSiteSettings, int simulationNr) {
this.context = context;
this.rocket = rocket;
this.launchSiteSettings = launchSiteSettings;
this.simulationNr = simulationNr;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
if (RASAeroCommonConstants.SUSTAINER_ENGINE.equals(element) || RASAeroCommonConstants.SUSTAINER_IGNITION_DELAY.equals(element)
|| RASAeroCommonConstants.BOOSTER1_ENGINE.equals(element) || RASAeroCommonConstants.BOOSTER1_IGNITION_DELAY.equals(element)
|| RASAeroCommonConstants.BOOSTER1_SEPARATION_DELAY.equals(element) || RASAeroCommonConstants.INCLUDE_BOOSTER1.equals(element)
|| RASAeroCommonConstants.BOOSTER2_ENGINE.equals(element) || RASAeroCommonConstants.BOOSTER2_IGNITION_DELAY.equals(element)
|| RASAeroCommonConstants.BOOSTER2_SEPARATION_DELAY.equals(element) || RASAeroCommonConstants.INCLUDE_BOOSTER2.equals(element)) {
return PlainTextHandler.INSTANCE;
}
return null;
}
@Override
public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
if (RASAeroCommonConstants.SUSTAINER_ENGINE.equals(element)) {
sustainerEngine = RASAeroMotorsLoader.getMotorFromRASAero(content, warnings);
} else if (RASAeroCommonConstants.SUSTAINER_IGNITION_DELAY.equals(element)) {
sustainerIgnitionDelay = Double.parseDouble(content);
} else if (RASAeroCommonConstants.BOOSTER1_ENGINE.equals(element)) {
booster1Engine = RASAeroMotorsLoader.getMotorFromRASAero(content, warnings);
} else if (RASAeroCommonConstants.BOOSTER1_IGNITION_DELAY.equals(element)) {
booster1IgnitionDelay = Double.parseDouble(content);
} else if (RASAeroCommonConstants.BOOSTER1_SEPARATION_DELAY.equals(element)) {
booster1SeparationDelay = Double.parseDouble(content);
} else if (RASAeroCommonConstants.INCLUDE_BOOSTER1.equals(element)) {
includeBooster1 = Boolean.parseBoolean(content);
} else if (RASAeroCommonConstants.BOOSTER2_ENGINE.equals(element)) {
booster2Engine = RASAeroMotorsLoader.getMotorFromRASAero(content, warnings);
} else if (RASAeroCommonConstants.BOOSTER2_IGNITION_DELAY.equals(element)) {
booster2IgnitionDelay = Double.parseDouble(content);
} else if (RASAeroCommonConstants.BOOSTER2_SEPARATION_DELAY.equals(element)) {
booster2SeparationDelay = Double.parseDouble(content);
} else if (RASAeroCommonConstants.INCLUDE_BOOSTER2.equals(element)) {
includeBooster2 = Boolean.parseBoolean(content);
}
}
@Override
public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
FlightConfigurationId id = new FlightConfigurationId();
rocket.createFlightConfiguration(id);
// Add motors to the rocket
double separationDelay = includeBooster1 && (booster1SeparationDelay != null) ? booster1SeparationDelay : 0.0;
addMotorToStage(0, sustainerEngine, sustainerIgnitionDelay, separationDelay, id, warnings);
separationDelay = includeBooster2 && (booster2SeparationDelay != null) ? booster2SeparationDelay : 0.0;
if (includeBooster1) {
addMotorToStage(1, booster1Engine, booster1IgnitionDelay, separationDelay, id, warnings);
}
if (includeBooster2) {
addMotorToStage(2, booster2Engine, booster2IgnitionDelay, 0.0, id, warnings);
}
// Add a new simulation
Simulation sim = new Simulation(rocket);
sim.setFlightConfigurationId(id);
sim.setName("Simulation " + simulationNr);
sim.copySimulationOptionsFrom(launchSiteSettings);
context.getOpenRocketDocument().addSimulation(sim);
}
private void addMotorToStage(final int stageNr, final Motor motor, final double ignitionDelay, final double separationDelay,
final FlightConfigurationId id, final WarningSet warnings) {
MotorMount mount = getMotorMountForStage(stageNr);
if (mount == null) {
warnings.add("No motor mount found for stage " + stageNr + ". Ignoring motor.");
return;
}
MotorConfiguration motorConfig = new MotorConfiguration(mount, id);
motorConfig.setMotor(motor);
motorConfig.setIgnitionDelay(ignitionDelay + separationDelay); // Just add the separation delay to the ignition delay
mount.setMotorConfig(motorConfig, id);
}
private MotorMount getMotorMountForStage(int stage) {
MotorMount mount = null;
for (RocketComponent component : rocket.getStage(stage)) {
if (component instanceof MotorMount) {
mount = (MotorMount) component;
}
}
if (mount != null) {
mount.setMotorMount(true);
}
return mount;
}
}
}