From fd2184784cb73ea1a43b856c92e66e5cdd1a8661 Mon Sep 17 00:00:00 2001 From: "Kae..pl" Date: Sun, 19 Jun 2011 22:50:12 +0000 Subject: [PATCH] Support for loading custom properties added. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7675 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../blender/helpers/v249/CurvesHelper.java | 10 + .../blender/helpers/v249/MeshHelper.java | 7 + .../blender/helpers/v249/ObjectHelper.java | 8 + .../blender/utils/AbstractBlenderHelper.java | 133 +++--- .../plugins/blender/utils/Properties.java | 441 ++++++++++++++++++ 5 files changed, 545 insertions(+), 54 deletions(-) create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/Properties.java diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java index 343fc1bdc..5eb429e36 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java @@ -30,6 +30,7 @@ import com.jme3.scene.plugins.blender.utils.BlenderInputStream; import com.jme3.scene.plugins.blender.utils.DataRepository; import com.jme3.scene.plugins.blender.utils.DynamicArray; import com.jme3.scene.plugins.blender.utils.Pointer; +import com.jme3.scene.plugins.blender.utils.Properties; import com.jme3.scene.shape.Curve; import com.jme3.scene.shape.Surface; import com.jme3.util.BufferUtils; @@ -203,6 +204,15 @@ public class CurvesHelper extends AbstractBlenderHelper { } } } + + //reading custom properties + Properties properties = this.loadProperties(curveStructure, dataRepository); + if(properties != null && properties.getValue() != null) { + for(Geometry geom : result) { + geom.setUserData("properties", properties); + } + } + return result; } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java index a2ea672b4..59e2461d0 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java @@ -58,6 +58,7 @@ import com.jme3.scene.plugins.blender.utils.DataRepository; import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.utils.DynamicArray; import com.jme3.scene.plugins.blender.utils.Pointer; +import com.jme3.scene.plugins.blender.utils.Properties; import com.jme3.texture.Texture; import com.jme3.util.BufferUtils; @@ -292,6 +293,9 @@ public class MeshHelper extends AbstractBlenderHelper { BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()]))); } + //reading custom properties + Properties properties = this.loadProperties(structure, dataRepository); + // generating meshes FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors); for (Entry> meshEntry : meshesMap.entrySet()) { @@ -364,6 +368,9 @@ public class MeshHelper extends AbstractBlenderHelper { } else { geometry.setMaterial(dataRepository.getDefaultMaterial()); } + if(properties != null && properties.getValue() != null) { + geometry.setUserData("properties", properties); + } geometries.add(geometry); } dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java index 19085d311..134a48bfd 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java @@ -62,6 +62,7 @@ import com.jme3.scene.plugins.blender.utils.DataRepository; import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.utils.DynamicArray; import com.jme3.scene.plugins.blender.utils.Pointer; +import com.jme3.scene.plugins.blender.utils.Properties; import com.jme3.scene.plugins.ogre.AnimData; /** @@ -259,6 +260,13 @@ public class ObjectHelper extends AbstractBlenderHelper { } finally { dataRepository.popParent(); } + + //reading custom properties + Properties properties = this.loadProperties(objectStructure, dataRepository); + if(properties != null && properties.getValue() != null) { + ((Node)result).setUserData("properties", properties); + } + if(result != null) { dataRepository.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java index 3bf0f08ab..26df4d388 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java @@ -34,6 +34,8 @@ package com.jme3.scene.plugins.blender.utils; import java.nio.FloatBuffer; import java.util.List; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; import com.jme3.util.BufferUtils; /** @@ -43,62 +45,85 @@ import com.jme3.util.BufferUtils; */ public abstract class AbstractBlenderHelper { - /** The version of the blend file. */ - protected final int blenderVersion; + /** The version of the blend file. */ + protected final int blenderVersion; - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * @param blenderVersion - * the version read from the blend file - */ - public AbstractBlenderHelper(String blenderVersion) { - this.blenderVersion = Integer.parseInt(blenderVersion); - } + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * @param blenderVersion + * the version read from the blend file + */ + public AbstractBlenderHelper(String blenderVersion) { + this.blenderVersion = Integer.parseInt(blenderVersion); + } - /** - * This method clears the state of the helper so that it can be used for different calculations of another feature. - */ - public void clearState() { - } + /** + * This method clears the state of the helper so that it can be used for different calculations of another feature. + */ + public void clearState() {} - /** - * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are - * being created and stored in the memory. It can be unwise especially inside loops. - * @param text - * the text to be checked - * @return true if the text is blank and false otherwise - */ - protected boolean isBlank(String text) { - if (text != null) { - for (int i = 0; i < text.length(); ++i) { - if (!Character.isWhitespace(text.charAt(i))) { - return false; - } - } - } - return true; - } + /** + * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are + * being created and stored in the memory. It can be unwise especially inside loops. + * @param text + * the text to be checked + * @return true if the text is blank and false otherwise + */ + protected boolean isBlank(String text) { + if (text != null) { + for (int i = 0; i < text.length(); ++i) { + if (!Character.isWhitespace(text.charAt(i))) { + return false; + } + } + } + return true; + } - /** - * Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length - * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc. - * @param data - * list of float[4] objects to place into a new FloatBuffer - */ - protected FloatBuffer createFloatBuffer(List data) { - if (data == null) { - return null; - } - FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size()); - for (float[] v : data) { - if (v != null) { - buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]); - } else { - buff.put(0).put(0).put(0).put(0); - } - } - buff.flip(); - return buff; - } + /** + * Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length + * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc. + * @param data + * list of float[4] objects to place into a new FloatBuffer + */ + protected FloatBuffer createFloatBuffer(List data) { + if (data == null) { + return null; + } + FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size()); + for (float[] v : data) { + if (v != null) { + buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]); + } else { + buff.put(0).put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * This method loads the properties if they are available and defined for the structure. + * @param structure + * the structure we read the properties from + * @param dataRepository + * the data repository + * @return loaded properties or null if they are not available + * @throws BlenderFileException + * an exception is thrown when the blend file is somehow corrupted + */ + protected Properties loadProperties(Structure structure, DataRepository dataRepository) throws BlenderFileException { + Properties properties = null; + Structure id = (Structure) structure.getFieldValue("ID"); + if (id != null) { + Pointer pProperties = (Pointer) id.getFieldValue("properties"); + if (!pProperties.isNull()) { + Structure propertiesStructure = pProperties.fetchData(dataRepository.getInputStream()).get(0); + properties = new Properties(); + properties.load(propertiesStructure, dataRepository); + } + } + return properties; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/Properties.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/Properties.java new file mode 100644 index 000000000..ed3a3e387 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/Properties.java @@ -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 dataList = group.evaluateListBase(dataRepository); + List subProperties = new ArrayList(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 arrays = pointer.fetchData(dataRepository.getInputStream()); + List result = new ArrayList(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) 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 groupProperties = (List) 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 descriptions = new HashMap(groupProperties.size()); + List propertiesRNA = (List) rnaUI.value; + for (Properties properties : propertiesRNA) { + String name = properties.name; + String description = null; + List rnaData = (List) 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; + } +}