|
@@ -0,0 +1,441 @@
|
|
|
+package com.jme3.scene.plugins.blender.utils;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.logging.Logger;
|
|
|
+
|
|
|
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
|
|
+
|
|
|
+import com.jme3.export.InputCapsule;
|
|
|
+import com.jme3.export.JmeExporter;
|
|
|
+import com.jme3.export.JmeImporter;
|
|
|
+import com.jme3.export.OutputCapsule;
|
|
|
+import com.jme3.export.Savable;
|
|
|
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
|
|
|
+import com.jme3.scene.plugins.blender.data.Structure;
|
|
|
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
|
|
|
+
|
|
|
+/**
|
|
|
+ * The blender object's custom properties.
|
|
|
+ * This class is valid for all versions of blender.
|
|
|
+ * @author Marcin Roguski (Kaelthas)
|
|
|
+ */
|
|
|
+public class Properties implements Cloneable, Savable {
|
|
|
+ private static final Logger LOGGER = Logger.getLogger(Properties.class.getName());
|
|
|
+
|
|
|
+ // property type
|
|
|
+ public static final int IDP_STRING = 0;
|
|
|
+ public static final int IDP_INT = 1;
|
|
|
+ public static final int IDP_FLOAT = 2;
|
|
|
+ public static final int IDP_ARRAY = 5;
|
|
|
+ public static final int IDP_GROUP = 6;
|
|
|
+ // public static final int IDP_ID = 7;//this is not implemented in blender (yet)
|
|
|
+ public static final int IDP_DOUBLE = 8;
|
|
|
+ // the following are valid for blender 2.5x+
|
|
|
+ public static final int IDP_IDPARRAY = 9;
|
|
|
+ public static final int IDP_NUMTYPES = 10;
|
|
|
+
|
|
|
+ protected static final String RNA_PROPERTY_NAME = "_RNA_UI";
|
|
|
+ /** Default name of the property (used if the name is not specified in blender file). */
|
|
|
+ protected static final String DEFAULT_NAME = "Unnamed property";
|
|
|
+
|
|
|
+ /** The name of the property. */
|
|
|
+ private String name;
|
|
|
+ /** The type of the property. */
|
|
|
+ private int type;
|
|
|
+ /** The subtype of the property. Defines the type of array's elements. */
|
|
|
+ private int subType;
|
|
|
+ /** The value of the property. */
|
|
|
+ private Object value;
|
|
|
+ /** The description of the property. */
|
|
|
+ private String description;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method loads the property from the belnder file.
|
|
|
+ * @param idPropertyStructure
|
|
|
+ * the ID structure constining the property
|
|
|
+ * @param dataRepository
|
|
|
+ * the data repository
|
|
|
+ * @throws BlenderFileException
|
|
|
+ * an exception is thrown when the belnder file is somehow invalid
|
|
|
+ */
|
|
|
+ public void load(Structure idPropertyStructure, DataRepository dataRepository) throws BlenderFileException {
|
|
|
+ name = idPropertyStructure.getFieldValue("name").toString();
|
|
|
+ if (name == null || name.length() == 0) {
|
|
|
+ name = DEFAULT_NAME;
|
|
|
+ }
|
|
|
+ subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue();
|
|
|
+ type = ((Number) idPropertyStructure.getFieldValue("type")).intValue();
|
|
|
+
|
|
|
+ // reading the data
|
|
|
+ Structure data = (Structure) idPropertyStructure.getFieldValue("data");
|
|
|
+ int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue();
|
|
|
+ switch (type) {
|
|
|
+ case IDP_STRING: {
|
|
|
+ Pointer pointer = (Pointer) data.getFieldValue("pointer");
|
|
|
+ BlenderInputStream bis = dataRepository.getInputStream();
|
|
|
+ FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pointer.getOldMemoryAddress());
|
|
|
+ bis.setPosition(dataFileBlock.getBlockPosition());
|
|
|
+ value = bis.readString();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IDP_INT:
|
|
|
+ int intValue = ((Number) data.getFieldValue("val")).intValue();
|
|
|
+ value = Integer.valueOf(intValue);
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ int floatValue = ((Number) data.getFieldValue("val")).intValue();
|
|
|
+ value = Float.valueOf(Float.intBitsToFloat(floatValue));
|
|
|
+ break;
|
|
|
+ case IDP_ARRAY: {
|
|
|
+ Pointer pointer = (Pointer) data.getFieldValue("pointer");
|
|
|
+ BlenderInputStream bis = dataRepository.getInputStream();
|
|
|
+ FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pointer.getOldMemoryAddress());
|
|
|
+ bis.setPosition(dataFileBlock.getBlockPosition());
|
|
|
+ int elementAmount = dataFileBlock.getSize();
|
|
|
+ switch (subType) {
|
|
|
+ case IDP_INT:
|
|
|
+ elementAmount /= 4;
|
|
|
+ int[] intList = new int[elementAmount];
|
|
|
+ for (int i = 0; i < elementAmount; ++i) {
|
|
|
+ intList[i] = bis.readInt();
|
|
|
+ }
|
|
|
+ value = intList;
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ elementAmount /= 4;
|
|
|
+ float[] floatList = new float[elementAmount];
|
|
|
+ for (int i = 0; i < elementAmount; ++i) {
|
|
|
+ floatList[i] = bis.readFloat();
|
|
|
+ }
|
|
|
+ value = floatList;
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ elementAmount /= 8;
|
|
|
+ double[] doubleList = new double[elementAmount];
|
|
|
+ for (int i = 0; i < elementAmount; ++i) {
|
|
|
+ doubleList[i] = bis.readDouble();
|
|
|
+ }
|
|
|
+ value = doubleList;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new IllegalStateException("Invalid array subtype: " + subType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case IDP_GROUP:
|
|
|
+ Structure group = (Structure) data.getFieldValue("group");
|
|
|
+ List<Structure> dataList = group.evaluateListBase(dataRepository);
|
|
|
+ List<Properties> subProperties = new ArrayList<Properties>(len);
|
|
|
+ for (Structure d : dataList) {
|
|
|
+ Properties properties = new Properties();
|
|
|
+ properties.load(d, dataRepository);
|
|
|
+ subProperties.add(properties);
|
|
|
+ }
|
|
|
+ value = subProperties;
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ int doublePart1 = ((Number) data.getFieldValue("val")).intValue();
|
|
|
+ int doublePart2 = ((Number) data.getFieldValue("val2")).intValue();
|
|
|
+ long doubleVal = (long) doublePart2 << 32 | doublePart1;
|
|
|
+ value = Double.valueOf(Double.longBitsToDouble(doubleVal));
|
|
|
+ break;
|
|
|
+ case IDP_IDPARRAY: {
|
|
|
+ Pointer pointer = (Pointer) data.getFieldValue("pointer");
|
|
|
+ List<Structure> arrays = pointer.fetchData(dataRepository.getInputStream());
|
|
|
+ List<Object> result = new ArrayList<Object>(arrays.size());
|
|
|
+ Properties temp = new Properties();
|
|
|
+ for (Structure array : arrays) {
|
|
|
+ temp.load(array, dataRepository);
|
|
|
+ result.add(temp.value);
|
|
|
+ }
|
|
|
+ this.value = result;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IDP_NUMTYPES:
|
|
|
+ throw new NotImplementedException();
|
|
|
+ // case IDP_ID://not yet implemented in blender
|
|
|
+ // return null;
|
|
|
+ default:
|
|
|
+ throw new IllegalStateException("Unknown custom property type: " + type);
|
|
|
+ }
|
|
|
+ this.completeLoading();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method returns the name of the property.
|
|
|
+ * @return the name of the property
|
|
|
+ */
|
|
|
+ public String getName() {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method returns the description of the property.
|
|
|
+ * @return the description of the property
|
|
|
+ */
|
|
|
+ public String getDescription() {
|
|
|
+ return description;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method returns the type of the property.
|
|
|
+ * @return the type of the property
|
|
|
+ */
|
|
|
+ public int getType() {
|
|
|
+ return type;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method returns the value of the property.
|
|
|
+ * The type of the value depends on the type of the property.
|
|
|
+ * @return the value of the property
|
|
|
+ */
|
|
|
+ public Object getValue() {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ this.append(sb, new StringBuilder());
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method appends the data of the property to the given string buffer.
|
|
|
+ * @param sb
|
|
|
+ * string buffer
|
|
|
+ * @param indent
|
|
|
+ * indent buffer
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void append(StringBuilder sb, StringBuilder indent) {
|
|
|
+ sb.append(indent).append("name: ").append(name).append("\n\r");
|
|
|
+ sb.append(indent).append("type: ").append(type).append("\n\r");
|
|
|
+ sb.append(indent).append("subType: ").append(subType).append("\n\r");
|
|
|
+ sb.append(indent).append("description: ").append(description).append("\n\r");
|
|
|
+ indent.append('\t');
|
|
|
+ sb.append(indent).append("value: ");
|
|
|
+ if (value instanceof Properties) {
|
|
|
+ ((Properties) value).append(sb, indent);
|
|
|
+ } else if (value instanceof List) {
|
|
|
+ for (Object v : (List<Object>) value) {
|
|
|
+ if (v instanceof Properties) {
|
|
|
+ sb.append(indent).append("{\n\r");
|
|
|
+ indent.append('\t');
|
|
|
+ ((Properties) v).append(sb, indent);
|
|
|
+ indent.deleteCharAt(indent.length() - 1);
|
|
|
+ sb.append(indent).append("}\n\r");
|
|
|
+ } else {
|
|
|
+ sb.append(v);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sb.append(value);
|
|
|
+ }
|
|
|
+ sb.append("\n\r");
|
|
|
+ indent.deleteCharAt(indent.length() - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method should be called after the properties loading.
|
|
|
+ * It loads the properties from the _RNA_UI property and removes this property from the
|
|
|
+ * result list.
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ protected void completeLoading() {
|
|
|
+ if (this.type == IDP_GROUP) {
|
|
|
+ List<Properties> groupProperties = (List<Properties>) this.value;
|
|
|
+ Properties rnaUI = null;
|
|
|
+ for (Properties properties : groupProperties) {
|
|
|
+ if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) {
|
|
|
+ rnaUI = properties;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (rnaUI != null) {
|
|
|
+ // removing the RNA from the result list
|
|
|
+ groupProperties.remove(rnaUI);
|
|
|
+
|
|
|
+ // loading the descriptions
|
|
|
+ Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size());
|
|
|
+ List<Properties> propertiesRNA = (List<Properties>) rnaUI.value;
|
|
|
+ for (Properties properties : propertiesRNA) {
|
|
|
+ String name = properties.name;
|
|
|
+ String description = null;
|
|
|
+ List<Properties> rnaData = (List<Properties>) properties.value;
|
|
|
+ for (Properties rna : rnaData) {
|
|
|
+ if ("description".equalsIgnoreCase(rna.name)) {
|
|
|
+ description = (String) rna.value;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ descriptions.put(name, description);
|
|
|
+ }
|
|
|
+
|
|
|
+ // applying the descriptions
|
|
|
+ for (Properties properties : groupProperties) {
|
|
|
+ properties.description = descriptions.get(properties.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @SuppressWarnings("rawtypes")
|
|
|
+ public void write(JmeExporter ex) throws IOException {
|
|
|
+ OutputCapsule oc = ex.getCapsule(this);
|
|
|
+ oc.write(name, "name", DEFAULT_NAME);
|
|
|
+ oc.write(type, "type", 0);
|
|
|
+ oc.write(subType, "subtype", 0);
|
|
|
+ oc.write(description, "description", null);
|
|
|
+ switch (type) {
|
|
|
+ case IDP_STRING:
|
|
|
+ oc.write((String) value, "value", null);
|
|
|
+ break;
|
|
|
+ case IDP_INT:
|
|
|
+ oc.write((Integer) value, "value", 0);
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ oc.write((Float) value, "value", 0);
|
|
|
+ break;
|
|
|
+ case IDP_ARRAY:
|
|
|
+ switch (subType) {
|
|
|
+ case IDP_INT:
|
|
|
+ oc.write((int[]) value, "value", null);
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ oc.write((float[]) value, "value", null);
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ oc.write((double[]) value, "value", null);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ LOGGER.warning("Cannot save the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType);
|
|
|
+ }
|
|
|
+ case IDP_GROUP:
|
|
|
+ oc.write((Properties) value, "value", null);
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ oc.write((Double) value, "value", 0);
|
|
|
+ break;
|
|
|
+ case IDP_IDPARRAY:
|
|
|
+ oc.writeSavableArrayList((ArrayList) value, "value", null);
|
|
|
+ break;
|
|
|
+ case IDP_NUMTYPES:
|
|
|
+ LOGGER.warning("Numtypes value not supported! Cannot write it!");
|
|
|
+ break;
|
|
|
+ // case IDP_ID://not yet implemented in blender
|
|
|
+ // break;
|
|
|
+ default:
|
|
|
+ LOGGER.warning("Cannot save the property's value! Invalid type! Property: name: " + name + "; type: " + type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void read(JmeImporter im) throws IOException {
|
|
|
+ InputCapsule ic = im.getCapsule(this);
|
|
|
+ name = ic.readString("name", DEFAULT_NAME);
|
|
|
+ type = ic.readInt("type", 0);
|
|
|
+ subType = ic.readInt("subtype", 0);
|
|
|
+ description = ic.readString("description", null);
|
|
|
+ switch (type) {
|
|
|
+ case IDP_STRING:
|
|
|
+ value = ic.readString("value", null);
|
|
|
+ break;
|
|
|
+ case IDP_INT:
|
|
|
+ value = Integer.valueOf(ic.readInt("value", 0));
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ value = Float.valueOf(ic.readFloat("value", 0.0f));
|
|
|
+ break;
|
|
|
+ case IDP_ARRAY:
|
|
|
+ switch (subType) {
|
|
|
+ case IDP_INT:
|
|
|
+ value = ic.readIntArray("value", null);
|
|
|
+ break;
|
|
|
+ case IDP_FLOAT:
|
|
|
+ value = ic.readFloatArray("value", null);
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ value = ic.readDoubleArray("value", null);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ LOGGER.warning("Cannot read the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType);
|
|
|
+ }
|
|
|
+ case IDP_GROUP:
|
|
|
+ value = ic.readSavable("value", null);
|
|
|
+ break;
|
|
|
+ case IDP_DOUBLE:
|
|
|
+ value = Double.valueOf(ic.readDouble("value", 0.0));
|
|
|
+ break;
|
|
|
+ case IDP_IDPARRAY:
|
|
|
+ value = ic.readSavableArrayList("value", null);
|
|
|
+ break;
|
|
|
+ case IDP_NUMTYPES:
|
|
|
+ LOGGER.warning("Numtypes value not supported! Cannot read it!");
|
|
|
+ break;
|
|
|
+ // case IDP_ID://not yet implemented in blender
|
|
|
+ // break;
|
|
|
+ default:
|
|
|
+ LOGGER.warning("Cannot read the property's value! Invalid type! Property: name: " + name + "; type: " + type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ final int prime = 31;
|
|
|
+ int result = 1;
|
|
|
+ result = prime * result + (description == null ? 0 : description.hashCode());
|
|
|
+ result = prime * result + (name == null ? 0 : name.hashCode());
|
|
|
+ result = prime * result + subType;
|
|
|
+ result = prime * result + type;
|
|
|
+ result = prime * result + (value == null ? 0 : value.hashCode());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object obj) {
|
|
|
+ if (this == obj) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (obj == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (this.getClass() != obj.getClass()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Properties other = (Properties) obj;
|
|
|
+ if (description == null) {
|
|
|
+ if (other.description != null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else if (!description.equals(other.description)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (name == null) {
|
|
|
+ if (other.name != null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else if (!name.equals(other.name)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (subType != other.subType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (type != other.type) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (value == null) {
|
|
|
+ if (other.value != null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else if (!value.equals(other.value)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|