- Implemented a DampingMoment simulation listener example
[debian/openrocket] / core / src / net / sf / openrocket / file / openrocket / importt / OpenRocketLoader.java
index 071d5710afabe90010389a358341a9209b214652..4efdf9dbd5f962b15ce2bccbec136884c2796d63 100644 (file)
@@ -7,6 +7,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -18,13 +19,16 @@ import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.document.Simulation.Status;
 import net.sf.openrocket.document.StorageOptions;
 import net.sf.openrocket.file.AbstractRocketLoader;
+import net.sf.openrocket.file.MotorFinder;
 import net.sf.openrocket.file.RocketLoadException;
+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.file.simplesax.SimpleSAX;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.preset.ComponentPreset;
 import net.sf.openrocket.rocketcomponent.BodyComponent;
 import net.sf.openrocket.rocketcomponent.BodyTube;
 import net.sf.openrocket.rocketcomponent.Bulkhead;
@@ -69,6 +73,7 @@ import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.simulation.FlightEvent.Type;
 import net.sf.openrocket.simulation.SimulationOptions;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.BugException;
@@ -86,7 +91,7 @@ import org.xml.sax.SAXException;
  * Class that loads a rocket definition from an OpenRocket rocket file.
  * <p>
  * This class uses SAX to read the XML file format.  The 
- * {@link #loadFromStream(InputStream)} method simply sets the system up and 
+ * #loadFromStream(InputStream) method simply sets the system up and 
  * starts the parsing, while the actual logic is in the private inner class
  * <code>OpenRocketHandler</code>.
  * 
@@ -97,10 +102,11 @@ public class OpenRocketLoader extends AbstractRocketLoader {
        
        
        @Override
-       public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
+       public OpenRocketDocument loadFromStream(InputStream source, MotorFinder motorFinder) throws RocketLoadException,
                        IOException {
                log.info("Loading .ork file");
                DocumentLoadingContext context = new DocumentLoadingContext();
+               context.setMotorFinder(motorFinder);
                
                InputSource xmlSource = new InputSource(source);
                OpenRocketHandler handler = new OpenRocketHandler(context);
@@ -160,7 +166,7 @@ public class OpenRocketLoader extends AbstractRocketLoader {
 class DocumentConfig {
        
        /* Remember to update OpenRocketSaver as well! */
-       public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4" };
+       public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5" };
        
        /**
         * Divisor used in converting an integer version to the point-represented version.
@@ -232,6 +238,8 @@ class DocumentConfig {
                                Reflection.findMethod(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
                setters.put("RocketComponent:comment", new StringSetter(
                                Reflection.findMethod(RocketComponent.class, "setComment", String.class)));
+               setters.put("RocketComponent:preset", new ComponentPresetSetter(
+                               Reflection.findMethod(RocketComponent.class, "loadPreset", ComponentPreset.class)));
                
                // ExternalComponent
                setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
@@ -476,6 +484,14 @@ class DocumentConfig {
                                Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
                setters.put("Rocket:revision", new StringSetter(
                                Reflection.findMethod(Rocket.class, "setRevision", String.class)));
+               
+               // Stage
+               setters.put("Stage:separationevent", new EnumSetter<Stage.SeparationEvent>(
+                               Reflection.findMethod(Stage.class, "setSeparationEvent", Stage.SeparationEvent.class),
+                               Stage.SeparationEvent.class));
+               setters.put("Stage:separationdelay", new DoubleSetter(
+                               Reflection.findMethod(Stage.class, "setSeparationDelay", double.class)));
+               
        }
        
        
@@ -497,7 +513,7 @@ class DocumentConfig {
                        return null;
                name = name.trim();
                for (Enum<T> e : enumClass.getEnumConstants()) {
-                       if (e.name().toLowerCase().replace("_", "").equals(name)) {
+                       if (e.name().toLowerCase(Locale.ENGLISH).replace("_", "").equals(name)) {
                                return e;
                        }
                }
@@ -535,7 +551,7 @@ class DocumentConfig {
  * The starting point of the handlers.  Accepts a single <openrocket> element and hands
  * the contents to be read by a OpenRocketContentsHandler.
  */
-class OpenRocketHandler extends ElementHandler {
+class OpenRocketHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private OpenRocketContentHandler handler = null;
        
@@ -626,13 +642,14 @@ class OpenRocketHandler extends ElementHandler {
 /**
  * Handles the content of the <openrocket> tag.
  */
-class OpenRocketContentHandler extends ElementHandler {
+class OpenRocketContentHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final OpenRocketDocument doc;
        private final Rocket rocket;
        
        private boolean rocketDefined = false;
        private boolean simulationsDefined = false;
+       private boolean datatypesDefined = false;
        
        public OpenRocketContentHandler(DocumentLoadingContext context) {
                this.context = context;
@@ -640,7 +657,6 @@ class OpenRocketContentHandler extends ElementHandler {
                this.doc = new OpenRocketDocument(rocket);
        }
        
-       
        public OpenRocketDocument getDocument() {
                if (!rocketDefined)
                        return null;
@@ -662,6 +678,15 @@ class OpenRocketContentHandler extends ElementHandler {
                        return new ComponentParameterHandler(rocket, context);
                }
                
+               if (element.equals("datatypes")) {
+                       if (datatypesDefined) {
+                               warnings.add(Warning.fromString("Multiple datatype blocks. Ignoring later ones."));
+                               return null;
+                       }
+                       datatypesDefined = true;
+                       return new DatatypeHandler(this, context);
+               }
+               
                if (element.equals("simulations")) {
                        if (simulationsDefined) {
                                warnings.add(Warning
@@ -679,14 +704,96 @@ class OpenRocketContentHandler extends ElementHandler {
        }
 }
 
+class DatatypeHandler extends AbstractElementHandler {
+       private final DocumentLoadingContext context;
+       private final OpenRocketContentHandler contentHandler;
+       private CustomExpressionHandler customExpressionHandler = null;
+       
+       public DatatypeHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+               this.context = context;
+               this.contentHandler = contentHandler;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element,
+                       HashMap<String, String> attributes, WarningSet warnings)
+                       throws SAXException {
+               
+               if (element.equals("type") && attributes.get("source").equals("customexpression")) {
+                       customExpressionHandler = new CustomExpressionHandler(contentHandler, context);
+                       return customExpressionHandler;
+               }
+               else {
+                       warnings.add(Warning.fromString("Unknown datatype " + element + " defined, ignoring"));
+               }
+               
+               return this;
+       }
+       
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
+               attributes.remove("source");
+               super.closeElement(element, attributes, content, warnings);
+               
+               if (customExpressionHandler != null) {
+                       contentHandler.getDocument().addCustomExpression(customExpressionHandler.currentExpression);
+               }
+               
+       }
+       
+}
 
-
+class CustomExpressionHandler extends AbstractElementHandler {
+       private final DocumentLoadingContext context;
+       private final OpenRocketContentHandler contentHandler;
+       public CustomExpression currentExpression;
+       
+       public CustomExpressionHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+               this.context = context;
+               this.contentHandler = contentHandler;
+               currentExpression = new CustomExpression(contentHandler.getDocument());
+               
+       }
+       
+       @Override
+       public ElementHandler openElement(String element,
+                       HashMap<String, String> attributes, WarningSet warnings)
+                       throws SAXException {
+               
+               return this;
+       }
+       
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+               
+               if (element.equals("type")) {
+                       contentHandler.getDocument().addCustomExpression(currentExpression);
+               }
+               
+               if (element.equals("name")) {
+                       currentExpression.setName(content);
+               }
+               
+               if (element.equals("symbol")) {
+                       currentExpression.setSymbol(content);
+               }
+               
+               if (element.equals("unit") && attributes.get("unittype").equals("auto")) {
+                       currentExpression.setUnit(content);
+               }
+               
+               if (element.equals("expression")) {
+                       currentExpression.setExpression(content);
+               }
+       }
+}
 
 /**
  * A handler that creates components from the corresponding elements.  The control of the
  * contents is passed on to ComponentParameterHandler.
  */
-class ComponentHandler extends ElementHandler {
+class ComponentHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final RocketComponent parent;
        
@@ -730,7 +837,7 @@ class ComponentHandler extends ElementHandler {
  * This uses the setters, or delegates the handling to another handler for specific
  * elements.
  */
-class ComponentParameterHandler extends ElementHandler {
+class ComponentParameterHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final RocketComponent component;
        
@@ -811,7 +918,7 @@ class ComponentParameterHandler extends ElementHandler {
  * A handler that reads the <point> specifications within the freeformfinset's
  * <finpoints> elements.
  */
-class FinSetPointHandler extends ElementHandler {
+class FinSetPointHandler extends AbstractElementHandler {
        @SuppressWarnings("unused")
        private final DocumentLoadingContext context;
        private final FreeformFinSet finset;
@@ -863,7 +970,7 @@ class FinSetPointHandler extends ElementHandler {
 }
 
 
-class MotorMountHandler extends ElementHandler {
+class MotorMountHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final MotorMount mount;
        private MotorHandler motorHandler;
@@ -915,7 +1022,7 @@ class MotorMountHandler extends ElementHandler {
                if (element.equals("ignitionevent")) {
                        MotorMount.IgnitionEvent event = null;
                        for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) {
-                               if (e.name().toLowerCase().replaceAll("_", "").equals(content)) {
+                               if (e.name().toLowerCase(Locale.ENGLISH).replaceAll("_", "").equals(content)) {
                                        event = e;
                                        break;
                                }
@@ -959,7 +1066,7 @@ class MotorMountHandler extends ElementHandler {
 
 
 
-class MotorConfigurationHandler extends ElementHandler {
+class MotorConfigurationHandler extends AbstractElementHandler {
        @SuppressWarnings("unused")
        private final DocumentLoadingContext context;
        private final Rocket rocket;
@@ -1018,11 +1125,10 @@ class MotorConfigurationHandler extends ElementHandler {
 }
 
 
-class MotorHandler extends ElementHandler {
+class MotorHandler extends AbstractElementHandler {
        /** File version where latest digest format was introduced */
        private static final int MOTOR_DIGEST_VERSION = 104;
        
-       @SuppressWarnings("unused")
        private final DocumentLoadingContext context;
        private Motor.Type type = null;
        private String manufacturer = null;
@@ -1048,73 +1154,7 @@ class MotorHandler extends ElementHandler {
         * Return the motor to use, or null.
         */
        public Motor getMotor(WarningSet warnings) {
-               if (designation == null) {
-                       warnings.add(Warning.fromString("No motor specified, ignoring."));
-                       return null;
-               }
-               
-               List<? extends Motor> motors = Application.getMotorSetDatabase().findMotors(type, manufacturer,
-                               designation, diameter, length);
-               
-               // No motors
-               if (motors.size() == 0) {
-                       Warning.MissingMotor mmw = new Warning.MissingMotor();
-                       mmw.setDesignation(designation);
-                       mmw.setDigest(digest);
-                       mmw.setDiameter(diameter);
-                       mmw.setLength(length);
-                       mmw.setManufacturer(manufacturer);
-                       mmw.setType(type);
-                       warnings.add(mmw);
-                       return null;
-               }
-               
-               // One motor
-               if (motors.size() == 1) {
-                       Motor m = motors.get(0);
-                       if (digest != null && !digest.equals(m.getDigest())) {
-                               String str = "Motor with designation '" + designation + "'";
-                               if (manufacturer != null)
-                                       str += " for manufacturer '" + manufacturer + "'";
-                               str += " has differing thrust curve than the original.";
-                               warnings.add(str);
-                       }
-                       return m;
-               }
-               
-               // Multiple motors, check digest for which one to use
-               if (digest != null) {
-                       
-                       // Check for motor with correct digest
-                       for (Motor m : motors) {
-                               if (digest.equals(m.getDigest())) {
-                                       return m;
-                               }
-                       }
-                       String str = "Motor with designation '" + designation + "'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       str += " has differing thrust curve than the original.";
-                       warnings.add(str);
-                       
-               } else {
-                       
-                       // No digest, check for preferred digest (OpenRocket <= 1.1.0)
-                       // TODO: MEDIUM: This should only be done for document versions 1.1 and below
-                       for (Motor m : motors) {
-                               if (PreferredMotorDigests.DIGESTS.contains(m.getDigest())) {
-                                       return m;
-                               }
-                       }
-                       
-                       String str = "Multiple motors with designation '" + designation + "'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       str += " found, one chosen arbitrarily.";
-                       warnings.add(str);
-                       
-               }
-               return motors.get(0);
+               return context.getMotorFinder().findMotor(type, manufacturer, designation, diameter, length, digest, warnings);
        }
        
        /**
@@ -1140,7 +1180,7 @@ class MotorHandler extends ElementHandler {
                        // Motor type
                        type = null;
                        for (Motor.Type t : Motor.Type.values()) {
-                               if (t.name().toLowerCase().equals(content.trim())) {
+                               if (t.name().toLowerCase(Locale.ENGLISH).equals(content.trim())) {
                                        type = t;
                                        break;
                                }
@@ -1219,7 +1259,7 @@ class MotorHandler extends ElementHandler {
 
 
 
-class SimulationsHandler extends ElementHandler {
+class SimulationsHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final OpenRocketDocument doc;
        private SingleSimulationHandler handler;
@@ -1246,13 +1286,18 @@ class SimulationsHandler extends ElementHandler {
        public void closeElement(String element, HashMap<String, String> attributes,
                        String content, WarningSet warnings) throws SAXException {
                attributes.remove("status");
+               
+               //Finished loading. Rebuilding custom expressions in case something has changed such as listener variable come available.
+               for (CustomExpression exp : doc.getCustomExpressions()){
+                       exp.setExpression(exp.getExpressionString());
+               }
+               
                super.closeElement(element, attributes, content, warnings);
        }
-       
-       
 }
 
-class SingleSimulationHandler extends ElementHandler {
+class SingleSimulationHandler extends AbstractElementHandler {
+       
        private final DocumentLoadingContext context;
        
        private final OpenRocketDocument doc;
@@ -1269,7 +1314,9 @@ class SingleSimulationHandler extends ElementHandler {
                this.context = context;
        }
        
-       
+       public OpenRocketDocument getDocument() {
+               return doc;
+       }
        
        @Override
        public ElementHandler openElement(String element, HashMap<String, String> attributes,
@@ -1282,7 +1329,7 @@ class SingleSimulationHandler extends ElementHandler {
                        conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
                        return conditionHandler;
                } else if (element.equals("flightdata")) {
-                       dataHandler = new FlightDataHandler(context);
+                       dataHandler = new FlightDataHandler(this, context);
                        return dataHandler;
                } else {
                        warnings.add("Unknown element '" + element + "', ignoring.");
@@ -1345,9 +1392,7 @@ class SingleSimulationHandler extends ElementHandler {
        }
 }
 
-
-
-class SimulationConditionsHandler extends ElementHandler {
+class SimulationConditionsHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private SimulationOptions conditions;
        private AtmosphereHandler atmosphereHandler;
@@ -1459,7 +1504,7 @@ class SimulationConditionsHandler extends ElementHandler {
 }
 
 
-class AtmosphereHandler extends ElementHandler {
+class AtmosphereHandler extends AbstractElementHandler {
        @SuppressWarnings("unused")
        private final DocumentLoadingContext context;
        private final String model;
@@ -1524,18 +1569,20 @@ class AtmosphereHandler extends ElementHandler {
 }
 
 
-class FlightDataHandler extends ElementHandler {
+class FlightDataHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        
        private FlightDataBranchHandler dataHandler;
        private WarningSet warningSet = new WarningSet();
        private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
        
+       private SingleSimulationHandler simHandler;
        private FlightData data;
        
        
-       public FlightDataHandler(DocumentLoadingContext context) {
+       public FlightDataHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) {
                this.context = context;
+               this.simHandler = simHandler;
        }
        
        public FlightData getFlightData() {
@@ -1555,7 +1602,8 @@ class FlightDataHandler extends ElementHandler {
                                return null;
                        }
                        dataHandler = new FlightDataBranchHandler(attributes.get("name"),
-                                       attributes.get("types"), context);
+                                       attributes.get("types"),
+                                       simHandler, context);
                        return dataHandler;
                }
                
@@ -1646,24 +1694,65 @@ class FlightDataHandler extends ElementHandler {
 }
 
 
-class FlightDataBranchHandler extends ElementHandler {
+class FlightDataBranchHandler extends AbstractElementHandler {
        @SuppressWarnings("unused")
        private final DocumentLoadingContext context;
        private final FlightDataType[] types;
        private final FlightDataBranch branch;
        
-       public FlightDataBranchHandler(String name, String typeList, DocumentLoadingContext context) {
+       private static final LogHelper log = Application.getLogger();
+       private final SingleSimulationHandler simHandler;
+       
+       public FlightDataBranchHandler(String name, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
+               this.simHandler = simHandler;
                this.context = context;
                String[] split = typeList.split(",");
                types = new FlightDataType[split.length];
                for (int i = 0; i < split.length; i++) {
-                       types[i] = FlightDataType.getType(split[i], UnitGroup.UNITS_NONE);
+                       String typeName = split[i];
+                       FlightDataType matching = findFlightDataType(typeName);
+                       types[i] = matching;
+                       //types[i] = FlightDataType.getType(typeName, matching.getSymbol(), matching.getUnitGroup());
                }
                
                // TODO: LOW: May throw an IllegalArgumentException
                branch = new FlightDataBranch(name, types);
        }
        
+       // Find the full flight data type given name only
+       // Note: this way of doing it requires that custom expressions always come before flight data in the file,
+       // not the nicest but this is always the case anyway.
+       private FlightDataType findFlightDataType(String name) {
+               
+               // Kevins version with lookup by key. Not using right now
+               /*
+               if ( key != null ) {
+                       for (FlightDataType t : FlightDataType.ALL_TYPES){
+                               if (t.getKey().equals(key) ){
+                                       return t;
+                               }
+                       }
+               }
+               */
+               
+               // Look in built in types
+               for (FlightDataType t : FlightDataType.ALL_TYPES) {
+                       if (t.getName().equals(name)) {
+                               return t;
+                       }
+               }
+               
+               // Look in custom expressions
+               for (CustomExpression exp : simHandler.getDocument().getCustomExpressions()) {
+                       if (exp.getName().equals(name)) {
+                               return exp.getType();
+                       }
+               }
+               
+               log.warn("Could not find the flight data type '" + name + "' used in the XML file. Substituted type with unknown symbol and units.");
+               return FlightDataType.getType(name, "Unknown", UnitGroup.UNITS_NONE);
+       }
+       
        public FlightDataBranch getBranch() {
                branch.immute();
                return branch;
@@ -1989,8 +2078,71 @@ class ColorSetter implements Setter {
        }
 }
 
+////ComponentPresetSetter  -  sets a ComponentPreset value
+class ComponentPresetSetter implements Setter {
+       private final Reflection.Method setMethod;
+       
+       public ComponentPresetSetter(Reflection.Method set) {
+               this.setMethod = set;
+       }
+       
+       @Override
+       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               String manufacturerName = attributes.get("manufacturer");
+               if (manufacturerName == null) {
+                       warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no manufacturer specified.  Ignored"));
+                       return;
+               }
+               
+               String productNo = attributes.get("partno");
+               if (productNo == null) {
+                       warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no partno specified.  Ignored"));
+                       return;
+               }
+               
+               String digest = attributes.get("digest");
+               if (digest == null) {
+                       warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no digest specified."));
+               }
+               
+               String type = attributes.get("type");
+               if (type == null) {
+                       warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no type specified."));
+               }
+               
+               List<ComponentPreset> presets = Application.getComponentPresetDao().find(manufacturerName, productNo);
+               
+               ComponentPreset matchingPreset = null;
+               
+               for (ComponentPreset preset : presets) {
+                       if (digest != null && preset.getDigest().equals(digest)) {
+                               // Found one with matching digest.  Take it.
+                               matchingPreset = preset;
+                               break;
+                       }
+                       if (type != null && preset.getType().name().equals(type) && matchingPreset != null) {
+                               // Found the first one with matching type.
+                               matchingPreset = preset;
+                       }
+               }
+               
+               // Was any found?
+               if (matchingPreset == null) {
+                       warnings.add(Warning.fromString("No matching ComponentPreset for component " + c.getName() + " found matching " + manufacturerName + " " + productNo));
+                       return;
+               }
+               
+               if (digest != null && !matchingPreset.getDigest().equals(digest)) {
+                       warnings.add(Warning.fromString("ComponentPreset for component " + c.getName() + " has wrong digest"));
+               }
+               
+               setMethod.invoke(c, matchingPreset);
+       }
+}
 
 
+////MaterialSetter  -  sets a Material value
 class MaterialSetter implements Setter {
        private final Reflection.Method setMethod;
        private final Material.Type type;
@@ -2041,12 +2193,12 @@ class MaterialSetter implements Setter {
                
                // Check type if specified
                str = attributes.remove("type");
-               if (str != null && !type.name().toLowerCase().equals(str)) {
+               if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
                        warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
                        return;
                }
                
-               mat = Databases.findMaterial(type, name, density, false);
+               mat = Databases.findMaterial(type, name, density);
                
                setMethod.invoke(c, mat);
        }
@@ -2055,6 +2207,7 @@ class MaterialSetter implements Setter {
 
 
 
+
 class PositionSetter implements Setter {
        
        @Override