Task 124: .DAE File Format
Task 124: .DAE File Format
File Format Specifications for .DAE
The .DAE (Digital Asset Exchange) file format is based on the COLLADA specification, which is an XML-based schema for exchanging 3D assets between applications. It is managed by the Khronos Group and supports elements for geometry, animation, shaders, physics, and metadata. The format is text-based (XML), with a root element <COLLADA>
and a version attribute (e.g., 1.4.1 or 1.5.0). Key versions include COLLADA 1.4 (widely adopted) and 1.5 (with enhancements like geolocation and kinematics). The official specifications are available as PDFs:
- COLLADA 1.4.1: https://www.khronos.org/files/collada_spec_1_4.pdf
- COLLADA 1.5.0: https://www.khronos.org/files/collada_spec_1_5.pdf
List of Properties Intrinsic to the .DAE File Format
These properties are derived from the <asset>
element in the COLLADA schema, which provides metadata intrinsic to the file. The <asset>
element is optional but commonly used for asset management, including authorship, timestamps, and coordinate system details. Below is a comprehensive list of properties (child elements and their sub-properties) from the specification:
- Contributor (0 or more instances): Represents authorship and tool information.
- Author: The name of the author.
- Author Email: The author's email address.
- Author Website: The author's website URL.
- Authoring Tool: The name of the tool used to create the asset.
- Comments: Additional comments from the contributor.
- Copyright: Copyright information.
- Source Data: URI to source data.
- Coverage (0 or 1): Defines spatial coverage.
- Geographic Location:
- Longitude: Float value (-180.0 to 180.0).
- Latitude: Float value (-90.0 to 90.0).
- Altitude: Float value, with mode (absolute or relativeToGround).
- Created: Date and time the asset was created (ISO 8601 xs:dateTime format).
- Keywords: Space-separated list of search keywords.
- Modified: Date and time the asset was last modified (ISO 8601 xs:dateTime format).
- Revision: Revision number or identifier.
- Subject: Topical description of the asset.
- Title: Title of the asset.
- Unit: Defines the unit of distance.
- Name: String (e.g., "meter").
- Meter: Float value representing meters per unit (default 1.0).
- Up Axis: Defines the upward axis (e.g., "Y_UP", "Z_UP").
These properties are not file system metadata (like size or permissions) but are embedded in the XML structure, making them intrinsic to the .DAE format itself.
Two Direct Download Links for .DAE Files
- https://raw.githubusercontent.com/pycollada/pycollada/collada15spec/examples/daeview/data/duck_triangles.dae (a classic COLLADA sample file featuring a duck model).
- https://raw.githubusercontent.com/assimp/assimp/master/test/models/Collada/sphere.dae (a sample sphere model for testing COLLADA importers).
HTML/JavaScript for Drag-and-Drop .DAE Property Dump
Below is a self-contained HTML page with embedded JavaScript. It allows users to drag and drop a .DAE file, parses the XML, extracts the properties from the <asset>
element, and dumps them to the screen in a readable format. (Note: This assumes modern browsers with FileReader and DOMParser support.)
Python Class for .DAE Properties
Below is a Python class that opens a .DAE file, decodes/parses the XML, reads and prints the properties to console, and includes a method to write a modified file with updated properties.
import xml.etree.ElementTree as ET
from datetime import datetime
class DAEProcessor:
NS = '{http://www.collada.org/2005/11/COLLADASchema}' # For COLLADA 1.4
NS15 = '{http://www.collada.org/2008/03/COLLADASchema}' # For COLLADA 1.5
def __init__(self, filepath):
self.filepath = filepath
self.tree = None
self.root = None
self.asset = None
self._load()
def _load(self):
self.tree = ET.parse(self.filepath)
self.root = self.tree.getroot()
# Try both namespaces
self.asset = self.root.find('asset') or self.root.find(self.NS + 'asset') or self.root.find(self.NS15 + 'asset')
def print_properties(self):
if not self.asset:
print("No <asset> element found.")
return
print("DAE Properties:")
# Contributors
contributors = self.asset.findall('contributor') or self.asset.findall(self.NS + 'contributor') or self.asset.findall(self.NS15 + 'contributor')
for i, contrib in enumerate(contributors, 1):
print(f"\nContributor {i}:")
print(f" Author: {contrib.findtext('author') or contrib.findtext(self.NS + 'author') or contrib.findtext(self.NS15 + 'author') or 'N/A'}")
print(f" Author Email: {contrib.findtext('author_email') or contrib.findtext(self.NS + 'author_email') or contrib.findtext(self.NS15 + 'author_email') or 'N/A'}")
print(f" Author Website: {contrib.findtext('author_website') or contrib.findtext(self.NS + 'author_website') or contrib.findtext(self.NS15 + 'author_website') or 'N/A'}")
print(f" Authoring Tool: {contrib.findtext('authoring_tool') or contrib.findtext(self.NS + 'authoring_tool') or contrib.findtext(self.NS15 + 'authoring_tool') or 'N/A'}")
print(f" Comments: {contrib.findtext('comments') or contrib.findtext(self.NS + 'comments') or contrib.findtext(self.NS15 + 'comments') or 'N/A'}")
print(f" Copyright: {contrib.findtext('copyright') or contrib.findtext(self.NS + 'copyright') or contrib.findtext(self.NS15 + 'copyright') or 'N/A'}")
print(f" Source Data: {contrib.findtext('source_data') or contrib.findtext(self.NS + 'source_data') or contrib.findtext(self.NS15 + 'source_data') or 'N/A'}")
# Geographic Location
geo = self.asset.find('.//geographic_location') or self.asset.find(self.NS + './/geographic_location') or self.asset.find(self.NS15 + './/geographic_location')
if geo:
print("\nGeographic Location:")
print(f" Longitude: {geo.findtext('longitude') or geo.findtext(self.NS + 'longitude') or geo.findtext(self.NS15 + 'longitude') or 'N/A'}")
print(f" Latitude: {geo.findtext('latitude') or geo.findtext(self.NS + 'latitude') or geo.findtext(self.NS15 + 'latitude') or 'N/A'}")
alt = geo.find('altitude') or geo.find(self.NS + 'altitude') or geo.find(self.NS15 + 'altitude')
print(f" Altitude: {alt.text if alt else 'N/A'} (Mode: {alt.get('mode') if alt else 'N/A'})")
# Other properties
print(f"\nCreated: {self.asset.findtext('created') or self.asset.findtext(self.NS + 'created') or self.asset.findtext(self.NS15 + 'created') or 'N/A'}")
print(f"Keywords: {self.asset.findtext('keywords') or self.asset.findtext(self.NS + 'keywords') or self.asset.findtext(self.NS15 + 'keywords') or 'N/A'}")
print(f"Modified: {self.asset.findtext('modified') or self.asset.findtext(self.NS + 'modified') or self.asset.findtext(self.NS15 + 'modified') or 'N/A'}")
print(f"Revision: {self.asset.findtext('revision') or self.asset.findtext(self.NS + 'revision') or self.asset.findtext(self.NS15 + 'revision') or 'N/A'}")
print(f"Subject: {self.asset.findtext('subject') or self.asset.findtext(self.NS + 'subject') or self.asset.findtext(self.NS15 + 'subject') or 'N/A'}")
print(f"Title: {self.asset.findtext('title') or self.asset.findtext(self.NS + 'title') or self.asset.findtext(self.NS15 + 'title') or 'N/A'}")
# Unit
unit = self.asset.find('unit') or self.asset.find(self.NS + 'unit') or self.asset.find(self.NS15 + 'unit')
print(f"Unit: Name={unit.get('name') if unit else 'N/A'}, Meter={unit.get('meter') if unit else 'N/A'}")
# Up Axis
print(f"Up Axis: {self.asset.findtext('up_axis') or self.asset.findtext(self.NS + 'up_axis') or self.asset.findtext(self.NS15 + 'up_axis') or 'N/A'}")
def update_and_write(self, new_filepath, updates={}):
if not self.asset:
print("No <asset> to update.")
return
# Example updates: {'created': new_value, 'modified': datetime.now().isoformat(), etc.}
for key, value in updates.items():
elem = self.asset.find(key) or self.asset.find(self.NS + key) or self.asset.find(self.NS15 + key)
if elem is not None:
elem.text = value
self.tree.write(new_filepath, encoding='utf-8', xml_declaration=True)
# Usage example:
# processor = DAEProcessor('example.dae')
# processor.print_properties()
# processor.update_and_write('modified.dae', {'modified': datetime.now().isoformat()})
Java Class for .DAE Properties
Below is a Java class that opens a .DAE file, decodes/parses the XML, reads and prints the properties to console, and includes a method to write a modified file.
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DAEProcessor {
private Document doc;
private Element asset;
public DAEProcessor(String filepath) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(new File(filepath));
asset = (Element) doc.getElementsByTagName("asset").item(0);
}
public void printProperties() {
if (asset == null) {
System.out.println("No <asset> element found.");
return;
}
System.out.println("DAE Properties:");
// Contributors
NodeList contributors = asset.getElementsByTagName("contributor");
for (int i = 0; i < contributors.getLength(); i++) {
Element contrib = (Element) contributors.item(i);
System.out.println("\nContributor " + (i + 1) + ":");
System.out.println(" Author: " + getText(contrib, "author"));
System.out.println(" Author Email: " + getText(contrib, "author_email"));
System.out.println(" Author Website: " + getText(contrib, "author_website"));
System.out.println(" Authoring Tool: " + getText(contrib, "authoring_tool"));
System.out.println(" Comments: " + getText(contrib, "comments"));
System.out.println(" Copyright: " + getText(contrib, "copyright"));
System.out.println(" Source Data: " + getText(contrib, "source_data"));
}
// Geographic Location
Element geo = (Element) asset.getElementsByTagName("geographic_location").item(0);
if (geo != null) {
System.out.println("\nGeographic Location:");
System.out.println(" Longitude: " + getText(geo, "longitude"));
System.out.println(" Latitude: " + getText(geo, "latitude"));
Element alt = (Element) geo.getElementsByTagName("altitude").item(0);
System.out.println(" Altitude: " + (alt != null ? alt.getTextContent() : "N/A") + " (Mode: " + (alt != null ? alt.getAttribute("mode") : "N/A") + ")");
}
// Other properties
System.out.println("\nCreated: " + getText(asset, "created"));
System.out.println("Keywords: " + getText(asset, "keywords"));
System.out.println("Modified: " + getText(asset, "modified"));
System.out.println("Revision: " + getText(asset, "revision"));
System.out.println("Subject: " + getText(asset, "subject"));
System.out.println("Title: " + getText(asset, "title"));
// Unit
Element unit = (Element) asset.getElementsByTagName("unit").item(0);
System.out.println("Unit: Name=" + (unit != null ? unit.getAttribute("name") : "N/A") + ", Meter=" + (unit != null ? unit.getAttribute("meter") : "N/A"));
// Up Axis
System.out.println("Up Axis: " + getText(asset, "up_axis"));
}
private String getText(Element parent, String tag) {
NodeList nodes = parent.getElementsByTagName(tag);
return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : "N/A";
}
public void updateAndWrite(String newFilepath, String key, String value) throws Exception {
if (asset == null) {
System.out.println("No <asset> to update.");
return;
}
NodeList nodes = asset.getElementsByTagName(key);
if (nodes.getLength() > 0) {
nodes.item(0).setTextContent(value);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(newFilepath));
transformer.transform(source, result);
}
// Usage example:
// public static void main(String[] args) throws Exception {
// DAEProcessor processor = new DAEProcessor("example.dae");
// processor.printProperties();
// processor.updateAndWrite("modified.dae", "modified", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
// }
}
JavaScript Class for .DAE Properties
Below is a JavaScript class (for Node.js or browser with fs polyfill) that opens a .DAE file, decodes/parses the XML, reads and prints the properties to console, and includes a method to write a modified file. (For browser, adjust fs to local storage or blob.)
const fs = require('fs'); // For Node.js
const DOMParser = require('xmldom').DOMParser; // For Node.js; in browser, use window.DOMParser
const XMLSerializer = require('xmldom').XMLSerializer;
class DAEProcessor {
constructor(filepath) {
this.filepath = filepath;
this.doc = null;
this.asset = null;
this.load();
}
load() {
const xmlText = fs.readFileSync(this.filepath, 'utf-8');
const parser = new DOMParser();
this.doc = parser.parseFromString(xmlText);
this.asset = this.doc.getElementsByTagName('asset')[0];
}
printProperties() {
if (!this.asset) {
console.log('No <asset> element found.');
return;
}
console.log('DAE Properties:');
// Contributors
const contributors = this.asset.getElementsByTagName('contributor');
for (let i = 0; i < contributors.length; i++) {
const contrib = contributors[i];
console.log(`\nContributor ${i + 1}:`);
console.log(` Author: ${this.getText(contrib, 'author')}`);
console.log(` Author Email: ${this.getText(contrib, 'author_email')}`);
console.log(` Author Website: ${this.getText(contrib, 'author_website')}`);
console.log(` Authoring Tool: ${this.getText(contrib, 'authoring_tool')}`);
console.log(` Comments: ${this.getText(contrib, 'comments')}`);
console.log(` Copyright: ${this.getText(contrib, 'copyright')}`);
console.log(` Source Data: ${this.getText(contrib, 'source_data')}`);
}
// Geographic Location
const geo = this.asset.getElementsByTagName('geographic_location')[0];
if (geo) {
console.log('\nGeographic Location:');
console.log(` Longitude: ${this.getText(geo, 'longitude')}`);
console.log(` Latitude: ${this.getText(geo, 'latitude')}`);
const alt = geo.getElementsByTagName('altitude')[0];
console.log(` Altitude: ${alt ? alt.textContent : 'N/A'} (Mode: ${alt ? alt.getAttribute('mode') : 'N/A'})`);
}
// Other properties
console.log(`\nCreated: ${this.getText(this.asset, 'created')}`);
console.log(`Keywords: ${this.getText(this.asset, 'keywords')}`);
console.log(`Modified: ${this.getText(this.asset, 'modified')}`);
console.log(`Revision: ${this.getText(this.asset, 'revision')}`);
console.log(`Subject: ${this.getText(this.asset, 'subject')}`);
console.log(`Title: ${this.getText(this.asset, 'title')}`);
// Unit
const unit = this.asset.getElementsByTagName('unit')[0];
console.log(`Unit: Name=${unit ? unit.getAttribute('name') : 'N/A'}, Meter=${unit ? unit.getAttribute('meter') : 'N/A'}`);
// Up Axis
console.log(`Up Axis: ${this.getText(this.asset, 'up_axis')}`);
}
getText(parent, tag) {
const elem = parent.getElementsByTagName(tag)[0];
return elem ? elem.textContent : 'N/A';
}
updateAndWrite(newFilepath, key, value) {
if (!this.asset) {
console.log('No <asset> to update.');
return;
}
const elem = this.asset.getElementsByTagName(key)[0];
if (elem) {
elem.textContent = value;
}
const serializer = new XMLSerializer();
const xmlStr = serializer.serializeToString(this.doc);
fs.writeFileSync(newFilepath, xmlStr);
}
}
// Usage example (Node.js):
// const processor = new DAEProcessor('example.dae');
// processor.printProperties();
// processor.updateAndWrite('modified.dae', 'modified', new Date().toISOString());
C Class for .DAE Properties
Note: Standard C lacks built-in XML parsing, so this is implemented in C++ for practicality (using <fstream>
, <string>
, and simple string parsing for extraction, as a full XML parser like libxml2 is not assumed). It opens the file, reads the content, extracts properties via string searches (approximate for simplicity; not robust for all XML variations), prints to console, and includes a write method to modify and save.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <ctime> // For time in write
class DAEProcessor {
private:
std::string filepath;
std::string xmlContent;
std::string extractTag(const std::string& tag) {
size_t start = xmlContent.find("<" + tag + ">") ;
if (start == std::string::npos) return "N/A";
start += tag.length() + 2;
size_t end = xmlContent.find("</" + tag + ">", start);
if (end == std::string::npos) return "N/A";
return xmlContent.substr(start, end - start);
}
std::vector<std::string> extractContributors(const std::string& subTag) {
std::vector<std::string> results;
size_t pos = 0;
while ((pos = xmlContent.find("<contributor>", pos)) != std::string::npos) {
size_t subStart = xmlContent.find("<" + subTag + ">", pos);
if (subStart != std::string::npos) {
subStart += subTag.length() + 2;
size_t subEnd = xmlContent.find("</" + subTag + ">", subStart);
if (subEnd != std::string::npos) {
results.push_back(xmlContent.substr(subStart, subEnd - subStart));
}
}
pos += 13; // Skip to next
}
return results;
}
public:
DAEProcessor(const std::string& fp) : filepath(fp) {
std::ifstream file(fp);
if (file) {
xmlContent.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
}
}
void printProperties() {
if (xmlContent.empty()) {
std::cout << "Failed to load file." << std::endl;
return;
}
if (xmlContent.find("<asset>") == std::string::npos) {
std::cout << "No <asset> element found." << std::endl;
return;
}
std::cout << "DAE Properties:" << std::endl;
// Contributors (example for author and authoring_tool; extend similarly)
auto authors = extractContributors("author");
auto tools = extractContributors("authoring_tool");
// ... (similar for other sub-tags)
for (size_t i = 0; i < authors.size(); ++i) {
std::cout << "\nContributor " << (i + 1) << ":" << std::endl;
std::cout << " Author: " << (i < authors.size() ? authors[i] : "N/A") << std::endl;
std::cout << " Authoring Tool: " << (i < tools.size() ? tools[i] : "N/A") << std::endl;
// Add more sub-properties similarly
}
// Geographic (simplified; assume single)
std::cout << "\nGeographic Location:" << std::endl;
std::cout << " Longitude: " << extractTag("longitude") << std::endl;
std::cout << " Latitude: " << extractTag("latitude") << std::endl;
std::cout << " Altitude: " << extractTag("altitude") << std::endl; // Mode not extracted for simplicity
// Other properties
std::cout << "\nCreated: " << extractTag("created") << std::endl;
std::cout << "Keywords: " << extractTag("keywords") << std::endl;
std::cout << "Modified: " << extractTag("modified") << std::endl;
std::cout << "Revision: " << extractTag("revision") << std::endl;
std::cout << "Subject: " << extractTag("subject") << std::endl;
std::cout << "Title: " << extractTag("title") << std::endl;
// Unit (extract attributes manually)
size_t unitPos = xmlContent.find("<unit ");
std::string unitName = "N/A", unitMeter = "N/A";
if (unitPos != std::string::npos) {
size_t nameStart = xmlContent.find("name=\"", unitPos);
if (nameStart != std::string::npos) {
nameStart += 6;
size_t nameEnd = xmlContent.find("\"", nameStart);
unitName = xmlContent.substr(nameStart, nameEnd - nameStart);
}
size_t meterStart = xmlContent.find("meter=\"", unitPos);
if (meterStart != std::string::npos) {
meterStart += 7;
size_t meterEnd = xmlContent.find("\"", meterStart);
unitMeter = xmlContent.substr(meterStart, meterEnd - meterStart);
}
}
std::cout << "Unit: Name=" << unitName << ", Meter=" << unitMeter << std::endl;
std::cout << "Up Axis: " << extractTag("up_axis") << std::endl;
}
void updateAndWrite(const std::string& newFilepath, const std::string& key, const std::string& value) {
if (xmlContent.empty()) return;
size_t start = xmlContent.find("<" + key + ">");
if (start != std::string::npos) {
size_t end = xmlContent.find("</" + key + ">", start);
if (end != std::string::npos) {
xmlContent.replace(start + key.length() + 2, end - (start + key.length() + 2), value);
}
}
std::ofstream out(newFilepath);
out << xmlContent;
}
};
// Usage example:
// int main() {
// DAEProcessor processor("example.dae");
// processor.printProperties();
// time_t now = time(0);
// char buf[25];
// strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
// processor.updateAndWrite("modified.dae", "modified", buf);
// return 0;
// }