/* * MassObjectHandler.java */ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RocksimCommonConstants; import net.sf.openrocket.file.rocksim.RocksimDensityType; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.Coaxial; import net.sf.openrocket.rocketcomponent.MassComponent; import net.sf.openrocket.rocketcomponent.MassObject; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.ShockCord; import org.xml.sax.SAXException; /** * A SAX handler for Rocksim's MassObject XML type. */ class MassObjectHandler extends PositionDependentHandler<MassObject> { /** * The Rocksim Mass length fudge factor. Rocksim completely exaggerates the length of a mass object to the point * that it looks ridiculous in OpenRocket. This fudge factor is here merely to get the typical mass object to * render in the OpenRocket UI with it's bounds mostly inside it's parent. The odd thing about it is that Rocksim * does not expose the length of a mass object in the UI and actually treats mass objects as point objects - not 3 * or even 2 dimensional. */ public static final int MASS_LEN_FUDGE_FACTOR = 100; /** * The OpenRocket MassComponent - counterpart to the RS MassObject. */ private final MassComponent mass; /** * Reference to answer for getComponent(). */ private MassObject current; /** * Parent. */ private RocketComponent parent; /** * 0 == General, 1 == Shock Cord */ private int typeCode = 0; /** * Constructor. l * * @param c the parent component * @param warnings the warning set * * @throws IllegalArgumentException thrown if <code>c</code> is null */ public MassObjectHandler(DocumentLoadingContext context, RocketComponent c, WarningSet warnings) throws IllegalArgumentException { super(context); if (c == null) { throw new IllegalArgumentException("The parent component of a mass component may not be null."); } mass = new MassComponent(); current = mass; parent = c; } @Override public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) { return PlainTextHandler.INSTANCE; } @Override public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException { super.closeElement(element, attributes, content, warnings); try { if (RocksimCommonConstants.LEN.equals(element)) { mass.setLength(Double.parseDouble(content) / (RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH)); } if (RocksimCommonConstants.KNOWN_MASS.equals(element)) { mass.setComponentMass(Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS); } if (RocksimCommonConstants.KNOWN_CG.equals(element)) { //Setting the CG of the Mass Object to 0 is important because of the different ways that Rocksim and //OpenRocket treat mass objects. Rocksim treats them as points (even though the data file contains a //length) and because Rocksim sets the CG of the mass object to really be relative to the front of //the parent. But that value is already assumed in the position and position value for the component. //Thus it needs to be set to 0 to say that the mass object's CG is at the point of the mass object. super.setCG(0); } if (RocksimCommonConstants.TYPE_CODE.equals(element)) { typeCode = Integer.parseInt(content); } if (RocksimCommonConstants.MATERIAL.equals(element)) { setMaterialName(content); } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); } } @Override public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException { if (inferAsShockCord(typeCode, warnings)) { //Shock Cord mapMassObjectAsShockCord(element, attributes, content, warnings); } else { // typeCode == 0 General Mass Object if (isCompatible(parent, MassComponent.class, warnings)) { parent.addChild(mass); } super.endHandler(element, attributes, content, warnings); } } /** * Rocksim does not have a separate entity for Shock Cords. It has to be inferred. Sometimes the typeCode * indicates it's a shock cord, but most times it does not. This is due to bugs in the Rocksim Component and * Material databases. Try to infer a shock cord based on it's length and it's material type. It's somewhat * arbitrary, but if the mass object's length is more than twice the length of it's parent component and it's a LINE * material, then assume a shock cord. * * @param theTypeCode the code from the RKT XML file * * @return true if we think it's a shock cord */ private boolean inferAsShockCord(int theTypeCode, WarningSet warnings) { return (theTypeCode == 1 || (mass.getLength() >= 2 * parent.getLength() && RocksimDensityType.ROCKSIM_LINE .equals(getDensityType()))) && isCompatible(parent, ShockCord.class, warnings, true); } /** * If it appears that the mass object is a shock cord, then create an OR shock cord instance. * * @param element the element name * @param attributes the attributes * @param content the content of the element * @param warnings the warning set to store warnings in. * * @throws org.xml.sax.SAXException not thrown */ private void mapMassObjectAsShockCord(final String element, final HashMap<String, String> attributes, final String content, final WarningSet warnings) throws SAXException { ShockCord cord = new ShockCord(); current = cord; if (isCompatible(parent, ShockCord.class, warnings)) { parent.addChild(cord); } super.endHandler(element, attributes, content, warnings); cord.setName(mass.getName()); setOverride(cord, mass.isMassOverridden(), mass.getOverrideMass(), mass.getOverrideCGX()); cord.setRadialDirection(mass.getRadialDirection()); cord.setRadialPosition(mass.getRadialPosition()); cord.setRadius(mass.getRadius()); //Rocksim does not distinguish between total length of the cord and the packed length. Fudge the //packed length and set the real length. cord.setCordLength(mass.getLength()); cord.setLength(cord.getCordLength() / MASS_LEN_FUDGE_FACTOR); if (parent instanceof Coaxial) { Coaxial parentCoaxial = (Coaxial) parent; cord.setRadius(parentCoaxial.getInnerRadius()); } } /** * Get the component this handler is working upon. This changes depending upon the type of mass object. * * @return a component */ @Override public MassObject getComponent() { return current; } /** * Set the relative position onto the component. This cannot be done directly because setRelativePosition is not * public in all components. * * @param position the OpenRocket position */ public void setRelativePosition(RocketComponent.Position position) { current.setRelativePosition(position); } /** * Get the required type of material for this component. Does not apply to MassComponents, but does apply to Shock * Cords. * * @return LINE */ @Override public Material.Type getMaterialType() { return Material.Type.LINE; } }