5 package net.sf.openrocket.file.rocksim;
7 import net.sf.openrocket.aerodynamics.Warning;
8 import net.sf.openrocket.aerodynamics.WarningSet;
9 import net.sf.openrocket.document.OpenRocketDocument;
10 import net.sf.openrocket.file.simplesax.ElementHandler;
11 import net.sf.openrocket.file.simplesax.PlainTextHandler;
12 import net.sf.openrocket.rocketcomponent.Rocket;
13 import net.sf.openrocket.rocketcomponent.RocketComponent;
14 import net.sf.openrocket.rocketcomponent.Stage;
15 import org.xml.sax.SAXException;
17 import java.util.HashMap;
20 * This class is a Sax element handler for Rocksim version 9 design files. It parses the Rocksim file (typically
21 * a .rkt extension) and creates corresponding OpenRocket components. This is a best effort approach and may not
22 * be an exact replica.
24 * Limitations: Rocksim flight simulations are not imported; tube fins are not supported; Rocksim 'pods' are not supported.
26 public class RocksimHandler extends ElementHandler {
29 * Length conversion. Rocksim is in millimeters, OpenRocket in meters.
31 public static final int ROCKSIM_TO_OPENROCKET_LENGTH = 1000;
34 * Mass conversion. Rocksim is in grams, OpenRocket in kilograms.
36 public static final int ROCKSIM_TO_OPENROCKET_MASS = 1000;
39 * Density conversion. Rocksim is in milligrams/cubic centimeter, OpenRocket in grams/cubic centimeter.
41 public static final int ROCKSIM_TO_OPENROCKET_DENSITY = 1;
44 * Radius conversion. Rocksim is always in diameters, OpenRocket mostly in radius.
46 public static final int ROCKSIM_TO_OPENROCKET_RADIUS = 2 * ROCKSIM_TO_OPENROCKET_LENGTH;
49 * The main content handler.
51 private RocksimContentHandler handler = null;
54 * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
55 * has not been read yet.
57 * @return the document read, or null.
59 public OpenRocketDocument getDocument() {
60 return handler.getDocument();
64 public ElementHandler openElement(String element, HashMap<String, String> attributes,
65 WarningSet warnings) {
67 // Check for unknown elements
68 if (!element.equals("RockSimDocument")) {
69 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
73 // Check for first call
74 if (handler != null) {
75 warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
80 handler = new RocksimContentHandler();
87 * Handles the content of the <DesignInformation> tag.
89 class RocksimContentHandler extends ElementHandler {
91 * The OpenRocketDocument that is the container for the rocket.
93 private final OpenRocketDocument doc;
96 * The top-level component, from which all child components are added.
98 private final Rocket rocket;
101 * The rocksim file version.
103 private String version;
108 public RocksimContentHandler() {
109 this.rocket = new Rocket();
110 this.doc = new OpenRocketDocument(rocket);
114 * Get the OpenRocket document that has been created from parsing the Rocksim design file.
116 * @return the instantiated OpenRocketDocument
118 public OpenRocketDocument getDocument() {
123 public ElementHandler openElement(String element, HashMap<String, String> attributes,
124 WarningSet warnings) {
125 if ("DesignInformation".equals(element)) {
126 //The next sub-element is "RocketDesign", which is really the only thing that matters. Rather than
127 //create another handler just for that element, handle it here.
130 if ("FileVersion".equals(element)) {
131 return PlainTextHandler.INSTANCE;
133 if ("RocketDesign".equals(element)) {
134 return new RocketDesignHandler(rocket);
140 public void closeElement(String element, HashMap<String, String> attributes,
141 String content, WarningSet warnings) throws SAXException {
143 * SAX handler for Rocksim file version number. The value is not used currently, but could be used in the future
144 * for backward/forward compatibility reasons (different lower level handlers could be called via a strategy pattern).
146 if ("FileVersion".equals(element)) {
152 * Answer the file version.
154 * @return the version of the Rocksim design file
156 public String getVersion() {
163 * A SAX handler for the high level Rocksim design. This structure includes sub-structures for each of the stages.
164 * Correct functioning of this handler is predicated on the stage count element appearing before the actual stage parts
165 * structures. If that invariant is not true, then behavior will be unpredictable.
167 class RocketDesignHandler extends ElementHandler {
169 * The parent component.
171 private final RocketComponent component;
173 * The parsed stage count. Defaults to 1.
175 private int stageCount = 1;
177 * The overridden stage 1 mass.
179 private double stage1Mass = 0d;
181 * The overridden stage 2 mass.
183 private double stage2Mass = 0d;
185 * The overridden stage 3 mass.
187 private double stage3Mass = 0d;
189 * The overridden stage 1 Cg.
191 private double stage1CG = 0d;
193 * The overridden stage 2 Cg.
195 private double stage2CG = 0d;
197 * The overridden stage 3 Cg.
199 private double stage3CG = 0d;
204 * @param c the parent component
206 public RocketDesignHandler(RocketComponent c) {
211 public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
213 * In Rocksim stages are from the top down, so a single stage rocket is actually stage '3'. A 2-stage
214 * rocket defines stage '2' as the initial booster with stage '3' sitting atop it. And so on.
216 if ("Stage3Parts".equals(element)) {
217 final Stage stage = new Stage();
218 if (stage3Mass > 0.0d) {
219 stage.setMassOverridden(true);
220 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
221 stage.setOverrideMass(stage3Mass);
223 if (stage3CG > 0.0d) {
224 stage.setCGOverridden(true);
225 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
226 stage.setOverrideCGX(stage3CG);
228 component.addChild(stage);
229 return new StageHandler(stage);
231 if ("Stage2Parts".equals(element)) {
232 if (stageCount >= 2) {
233 final Stage stage = new Stage();
234 if (stage2Mass > 0.0d) {
235 stage.setMassOverridden(true);
236 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
237 stage.setOverrideMass(stage2Mass);
239 if (stage2CG > 0.0d) {
240 stage.setCGOverridden(true);
241 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
242 stage.setOverrideCGX(stage2CG);
244 component.addChild(stage);
245 return new StageHandler(stage);
248 if ("Stage1Parts".equals(element)) {
249 if (stageCount == 3) {
250 final Stage stage = new Stage();
251 if (stage1Mass > 0.0d) {
252 stage.setMassOverridden(true);
253 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
254 stage.setOverrideMass(stage1Mass);
256 if (stage1CG > 0.0d) {
257 stage.setCGOverridden(true);
258 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
259 stage.setOverrideCGX(stage1CG);
261 component.addChild(stage);
262 return new StageHandler(stage);
265 if ("Name".equals(element)) {
266 return PlainTextHandler.INSTANCE;
268 if ("StageCount".equals(element)) {
269 return PlainTextHandler.INSTANCE;
271 if ("Stage3Mass".equals(element)) {
272 return PlainTextHandler.INSTANCE;
274 if ("Stage2Mass".equals(element)) {
275 return PlainTextHandler.INSTANCE;
277 if ("Stage1Mass".equals(element)) {
278 return PlainTextHandler.INSTANCE;
280 if ("Stage3CG".equals(element)) {
281 return PlainTextHandler.INSTANCE;
283 if ("Stage2CGAlone".equals(element)) {
284 return PlainTextHandler.INSTANCE;
286 if ("Stage1CGAlone".equals(element)) {
287 return PlainTextHandler.INSTANCE;
293 public void closeElement(String element, HashMap<String, String> attributes,
294 String content, WarningSet warnings) throws SAXException {
296 if ("Name".equals(element)) {
297 component.setName(content);
299 if ("StageCount".equals(element)) {
300 stageCount = Integer.parseInt(content);
302 if ("Stage3Mass".equals(element)) {
303 stage3Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
305 if ("Stage2Mass".equals(element)) {
306 stage2Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
308 if ("Stage1Mass".equals(element)) {
309 stage1Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
311 if ("Stage3CG".equals(element)) {
312 stage3CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
314 if ("Stage2CGAlone".equals(element)) {
315 stage2CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
317 if ("Stage1CGAlone".equals(element)) {
318 stage1CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
321 catch (NumberFormatException nfe) {
322 warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number.");
329 * A SAX handler for a Rocksim stage.
331 class StageHandler extends ElementHandler {
333 * The parent OpenRocket component.
335 private final RocketComponent component;
340 * @param c the parent component
341 * @throws IllegalArgumentException thrown if <code>c</code> is null
343 public StageHandler(RocketComponent c) throws IllegalArgumentException {
345 throw new IllegalArgumentException("The stage component may not be null.");
351 public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
352 if ("NoseCone".equals(element)) {
353 return new NoseConeHandler(component);
355 if ("BodyTube".equals(element)) {
356 return new BodyTubeHandler(component);
358 if ("Transition".equals(element)) {
359 return new TransitionHandler(component);