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 * Bulk Density conversion. Rocksim is in kilograms/cubic meter, OpenRocket in kilograms/cubic meter.
41 public static final int ROCKSIM_TO_OPENROCKET_BULK_DENSITY = 1;
44 * Surface Density conversion. Rocksim is in grams/sq centimeter, OpenRocket in kilograms/sq meter. 1000/(100*100) = 1/10
46 public static final double ROCKSIM_TO_OPENROCKET_SURFACE_DENSITY = 1/10d;
49 * Line Density conversion. Rocksim is in kilograms/meter, OpenRocket in kilograms/meter.
51 public static final int ROCKSIM_TO_OPENROCKET_LINE_DENSITY = 1;
54 * Radius conversion. Rocksim is always in diameters, OpenRocket mostly in radius.
56 public static final int ROCKSIM_TO_OPENROCKET_RADIUS = 2 * ROCKSIM_TO_OPENROCKET_LENGTH;
59 * The main content handler.
61 private RocksimContentHandler handler = null;
64 * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
65 * has not been read yet.
67 * @return the document read, or null.
69 public OpenRocketDocument getDocument() {
70 return handler.getDocument();
74 public ElementHandler openElement(String element, HashMap<String, String> attributes,
75 WarningSet warnings) {
77 // Check for unknown elements
78 if (!element.equals("RockSimDocument")) {
79 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
83 // Check for first call
84 if (handler != null) {
85 warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
90 handler = new RocksimContentHandler();
97 * Handles the content of the <DesignInformation> tag.
99 class RocksimContentHandler extends ElementHandler {
101 * The OpenRocketDocument that is the container for the rocket.
103 private final OpenRocketDocument doc;
106 * The top-level component, from which all child components are added.
108 private final Rocket rocket;
111 * The rocksim file version.
113 private String version;
118 public RocksimContentHandler() {
119 this.rocket = new Rocket();
120 this.doc = new OpenRocketDocument(rocket);
124 * Get the OpenRocket document that has been created from parsing the Rocksim design file.
126 * @return the instantiated OpenRocketDocument
128 public OpenRocketDocument getDocument() {
133 public ElementHandler openElement(String element, HashMap<String, String> attributes,
134 WarningSet warnings) {
135 if ("DesignInformation".equals(element)) {
136 //The next sub-element is "RocketDesign", which is really the only thing that matters. Rather than
137 //create another handler just for that element, handle it here.
140 if ("FileVersion".equals(element)) {
141 return PlainTextHandler.INSTANCE;
143 if ("RocketDesign".equals(element)) {
144 return new RocketDesignHandler(rocket);
150 public void closeElement(String element, HashMap<String, String> attributes,
151 String content, WarningSet warnings) throws SAXException {
153 * SAX handler for Rocksim file version number. The value is not used currently, but could be used in the future
154 * for backward/forward compatibility reasons (different lower level handlers could be called via a strategy pattern).
156 if ("FileVersion".equals(element)) {
162 * Answer the file version.
164 * @return the version of the Rocksim design file
166 public String getVersion() {
173 * A SAX handler for the high level Rocksim design. This structure includes sub-structures for each of the stages.
174 * Correct functioning of this handler is predicated on the stage count element appearing before the actual stage parts
175 * structures. If that invariant is not true, then behavior will be unpredictable.
177 class RocketDesignHandler extends ElementHandler {
179 * The parent component.
181 private final RocketComponent component;
183 * The parsed stage count. Defaults to 1.
185 private int stageCount = 1;
187 * The overridden stage 1 mass.
189 private double stage1Mass = 0d;
191 * The overridden stage 2 mass.
193 private double stage2Mass = 0d;
195 * The overridden stage 3 mass.
197 private double stage3Mass = 0d;
199 * The overridden stage 1 Cg.
201 private double stage1CG = 0d;
203 * The overridden stage 2 Cg.
205 private double stage2CG = 0d;
207 * The overridden stage 3 Cg.
209 private double stage3CG = 0d;
214 * @param c the parent component
216 public RocketDesignHandler(RocketComponent c) {
221 public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
223 * In Rocksim stages are from the top down, so a single stage rocket is actually stage '3'. A 2-stage
224 * rocket defines stage '2' as the initial booster with stage '3' sitting atop it. And so on.
226 if ("Stage3Parts".equals(element)) {
227 final Stage stage = new Stage();
228 if (stage3Mass > 0.0d) {
229 stage.setMassOverridden(true);
230 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
231 stage.setOverrideMass(stage3Mass);
233 if (stage3CG > 0.0d) {
234 stage.setCGOverridden(true);
235 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
236 stage.setOverrideCGX(stage3CG);
238 component.addChild(stage);
239 return new StageHandler(stage);
241 if ("Stage2Parts".equals(element)) {
242 if (stageCount >= 2) {
243 final Stage stage = new Stage();
244 if (stage2Mass > 0.0d) {
245 stage.setMassOverridden(true);
246 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
247 stage.setOverrideMass(stage2Mass);
249 if (stage2CG > 0.0d) {
250 stage.setCGOverridden(true);
251 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
252 stage.setOverrideCGX(stage2CG);
254 component.addChild(stage);
255 return new StageHandler(stage);
258 if ("Stage1Parts".equals(element)) {
259 if (stageCount == 3) {
260 final Stage stage = new Stage();
261 if (stage1Mass > 0.0d) {
262 stage.setMassOverridden(true);
263 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
264 stage.setOverrideMass(stage1Mass);
266 if (stage1CG > 0.0d) {
267 stage.setCGOverridden(true);
268 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
269 stage.setOverrideCGX(stage1CG);
271 component.addChild(stage);
272 return new StageHandler(stage);
275 if ("Name".equals(element)) {
276 return PlainTextHandler.INSTANCE;
278 if ("StageCount".equals(element)) {
279 return PlainTextHandler.INSTANCE;
281 if ("Stage3Mass".equals(element)) {
282 return PlainTextHandler.INSTANCE;
284 if ("Stage2Mass".equals(element)) {
285 return PlainTextHandler.INSTANCE;
287 if ("Stage1Mass".equals(element)) {
288 return PlainTextHandler.INSTANCE;
290 if ("Stage3CG".equals(element)) {
291 return PlainTextHandler.INSTANCE;
293 if ("Stage2CGAlone".equals(element)) {
294 return PlainTextHandler.INSTANCE;
296 if ("Stage1CGAlone".equals(element)) {
297 return PlainTextHandler.INSTANCE;
303 public void closeElement(String element, HashMap<String, String> attributes,
304 String content, WarningSet warnings) throws SAXException {
306 if ("Name".equals(element)) {
307 component.setName(content);
309 if ("StageCount".equals(element)) {
310 stageCount = Integer.parseInt(content);
312 if ("Stage3Mass".equals(element)) {
313 stage3Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
315 if ("Stage2Mass".equals(element)) {
316 stage2Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
318 if ("Stage1Mass".equals(element)) {
319 stage1Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
321 if ("Stage3CG".equals(element)) {
322 stage3CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
324 if ("Stage2CGAlone".equals(element)) {
325 stage2CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
327 if ("Stage1CGAlone".equals(element)) {
328 stage1CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
331 catch (NumberFormatException nfe) {
332 warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number.");
339 * A SAX handler for a Rocksim stage.
341 class StageHandler extends ElementHandler {
343 * The parent OpenRocket component.
345 private final RocketComponent component;
350 * @param c the parent component
351 * @throws IllegalArgumentException thrown if <code>c</code> is null
353 public StageHandler(RocketComponent c) throws IllegalArgumentException {
355 throw new IllegalArgumentException("The stage component may not be null.");
361 public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
362 if ("NoseCone".equals(element)) {
363 return new NoseConeHandler(component);
365 if ("BodyTube".equals(element)) {
366 return new BodyTubeHandler(component);
368 if ("Transition".equals(element)) {
369 return new TransitionHandler(component);