package com.jme3.scene.plugins.blender.objects; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The blender object's custom properties. * This class is valid for all versions of blender. * @author Marcin Roguski (Kaelthas) */ public class Properties implements Cloneable { // 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 blenderContext * the blender context * @throws BlenderFileException * an exception is thrown when the belnder file is somehow invalid */ public void load(Structure idPropertyStructure, BlenderContext blenderContext) 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 = blenderContext.getInputStream(); FileBlockHeader dataFileBlock = blenderContext.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 = blenderContext.getInputStream(); FileBlockHeader dataFileBlock = blenderContext.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(); List<Properties> subProperties = new ArrayList<Properties>(len); for (Structure d : dataList) { Properties properties = new Properties(); properties.load(d, blenderContext); 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(); List<Object> result = new ArrayList<Object>(arrays.size()); Properties temp = new Properties(); for (Structure array : arrays) { temp.load(array, blenderContext); result.add(temp.value); } value = result; break; } case IDP_NUMTYPES: throw new UnsupportedOperationException(); // 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 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; } /** * @return the names of properties that are stored withing this property * (assuming this property is of IDP_GROUP type) */ @SuppressWarnings("unchecked") public List<String> getSubPropertiesNames() { List<String> result = null; if (type == IDP_GROUP) { List<Properties> properties = (List<Properties>) value; if (properties != null && properties.size() > 0) { result = new ArrayList<String>(properties.size()); for (Properties property : properties) { result.add(property.getName()); } } } return result; } /** * This method returns the same as getValue if the current property is of * other type than IDP_GROUP and its name matches 'propertyName' param. If * this property is a group property the method tries to find subproperty * value of the given name. The first found value is returnes os <b>use this * method wisely</b>. If no property of a given name is foung - <b>null</b> * is returned. * * @param propertyName * the name of the property * @return found property value or <b>null</b> */ @SuppressWarnings("unchecked") public Object findValue(String propertyName) { if (name.equals(propertyName)) { return value; } else { if (type == IDP_GROUP) { List<Properties> props = (List<Properties>) value; for (Properties p : props) { Object v = p.findValue(propertyName); if (v != null) { return v; } } } } return null; } @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 (type == IDP_GROUP) { List<Properties> groupProperties = (List<Properties>) 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 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; } }