Task 024: .AMF File Format
The Additive Manufacturing File Format (AMF) is an XML-based open standard for describing objects for additive manufacturing processes, such as 3D printing, defined by the ISO/ASTM 52915:2020 standard. Below, I address each part of your request systematically, focusing on the properties intrinsic to the AMF file format and providing classes in Python, Java, JavaScript, and C to handle AMF files.
1. Properties of the AMF File Format Intrinsic to Its File System
Based on the ISO/ASTM 52915:2020 standard and related sources, the AMF file format has the following intrinsic properties that define its structure and content within the file system:
- File Extension:
.amf
- Internet Media Type:
application/x-amf
- Format Type: XML-based, structured as a compressed ZIP archive retaining the
.amf
extension. - Encoding: UTF-8 (as specified in the XML declaration).
- Units: Specifies the unit system for measurements (e.g., millimeter, inch, feet, meter, micrometer). If not specified, millimeters are assumed.
- Structure:
- Root Element:
<amf>
encapsulates the entire file content. - Top-Level Elements (within
<amf>
): <object>
: Defines one or more 3D objects, each with a unique ID, containing:<mesh>
: Represents the geometry as a triangular mesh.<vertices>
: Lists points (x, y, z coordinates) that can be shared among volumes.<volume>
: Defines non-overlapping volumes within an object, with attributes like material ID and optional color.<triangle>
: Defines triangles within a volume, referencing vertex indices and optional color specifications.<material>
: Specifies material properties (e.g., material ID, name, or composite materials).<constellation>
: Defines arrangements of multiple objects with specified positions and orientations.<texture>
: Defines texture maps for applying patterns or images to surfaces.<metadata>
: Provides additional information (e.g., author, description, or custom data).- Geometry Representation: Uses triangular meshes to describe object surfaces, supporting curved triangles (optional conversion to flat triangles).
- Color Support: Native support for per-volume or per-triangle color specifications, including RGBA values or texture references.
- Material Support: Supports multiple materials, functionally graded materials, and composite materials.
- Compression: The file is stored as a ZIP-compressed archive to reduce file size, while retaining the
.amf
extension. - Interoperability: Strict adherence to an XML schema (XSD) for standards-compliant interoperability, available at specified ISO/ASTM URLs.
- Extensibility: Allows additional metadata and future extensions (e.g., suggested features like data integrity or encryption, though not currently specified).
These properties are intrinsic to the AMF file format as they define how the file is structured, stored, and interpreted by additive manufacturing systems, per the ISO/ASTM 52915:2020 standard.
2. Python Class for AMF File Handling
Below is a Python class that opens, decodes, reads, writes, and prints AMF file properties. It uses the xml.etree.ElementTree
module for XML parsing and zipfile
for handling the compressed AMF file.
import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO
class AMFFile:
def __init__(self, filename):
self.filename = filename
self.units = "millimeter" # Default
self.objects = []
self.materials = []
self.constellations = []
self.textures = []
self.metadata = []
def read(self):
"""Read and decode an AMF file, extracting its properties."""
try:
with zipfile.ZipFile(self.filename, 'r') as zf:
with zf.open(zf.namelist()[0]) as f: # Assume first file is the XML
tree = ET.parse(f)
root = tree.getroot()
# Extract units
self.units = root.get('unit', 'millimeter')
# Extract metadata
for meta in root.findall('metadata'):
self.metadata.append({
'type': meta.get('type'),
'value': meta.text
})
# Extract materials
for material in root.findall('material'):
self.materials.append({
'id': material.get('id'),
'name': material.find('name').text if material.find('name') is not None else None
})
# Extract objects
for obj in root.findall('object'):
obj_data = {'id': obj.get('id'), 'meshes': []}
for mesh in obj.findall('mesh'):
vertices = [(float(v.find('x').text), float(v.find('y').text), float(v.find('z').text))
for v in mesh.find('vertices').findall('vertex/coordinates')]
volumes = []
for volume in mesh.findall('volume'):
triangles = [(int(t.find('v1').text), int(t.find('v2').text), int(t.find('v3').text))
for t in volume.findall('triangle')]
volumes.append({
'materialid': volume.get('materialid'),
'triangles': triangles
})
obj_data['meshes'].append({'vertices': vertices, 'volumes': volumes})
self.objects.append(obj_data)
# Extract constellations
for const in root.findall('constellation'):
self.constellations.append({
'id': const.get('id'),
'instanceids': [inst.get('objectid') for inst in const.findall('instance')]
})
# Extract textures
for texture in root.findall('texture'):
self.textures.append({
'id': texture.get('id'),
'name': texture.find('name').text if texture.find('name') is not None else None
})
except Exception as e:
print(f"Error reading AMF file: {e}")
def write(self, output_filename):
"""Write the AMF properties to a new AMF file."""
try:
root = ET.Element('amf', unit=self.units)
for meta in self.metadata:
ET.SubElement(root, 'metadata', type=meta['type']).text = meta['value']
for material in self.materials:
mat_elem = ET.SubElement(root, 'material', id=material['id'])
if material['name']:
ET.SubElement(mat_elem, 'name').text = material['name']
for obj in self.objects:
obj_elem = ET.SubElement(root, 'object', id=obj['id'])
mesh_elem = ET.SubElement(obj_elem, 'mesh')
vertices_elem = ET.SubElement(mesh_elem, 'vertices')
for vertex in obj['meshes'][0]['vertices']:
v_elem = ET.SubElement(vertices_elem, 'vertex')
coord_elem = ET.SubElement(v_elem, 'coordinates')
ET.SubElement(coord_elem, 'x').text = str(vertex[0])
ET.SubElement(coord_elem, 'y').text = str(vertex[1])
ET.SubElement(coord_elem, 'z').text = str(vertex[2])
for volume in obj['meshes'][0]['volumes']:
vol_elem = ET.SubElement(mesh_elem, 'volume', materialid=volume['materialid'])
for triangle in volume['triangles']:
tri_elem = ET.SubElement(vol_elem, 'triangle')
ET.SubElement(tri_elem, 'v1').text = str(triangle[0])
ET.SubElement(tri_elem, 'v2').text = str(triangle[1])
ET.SubElement(tri_elem, 'v3').text = str(triangle[2])
for const in self.constellations:
const_elem = ET.SubElement(root, 'constellation', id=const['id'])
for inst_id in const['instanceids']:
ET.SubElement(const_elem, 'instance', objectid=inst_id)
for texture in self.textures:
tex_elem = ET.SubElement(root, 'texture', id=texture['id'])
if texture['name']:
ET.SubElement(tex_elem, 'name').text = texture['name']
# Write to a temporary XML file
tree = ET.ElementTree(root)
with BytesIO() as xml_buffer:
tree.write(xml_buffer, encoding='utf-8', xml_declaration=True)
with zipfile.ZipFile(output_filename, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('model.amf', xml_buffer.getvalue())
except Exception as e:
print(f"Error writing AMF file: {e}")
def print_properties(self):
"""Print all AMF properties to the console."""
print(f"AMF File: {self.filename}")
print(f"Units: {self.units}")
print("Metadata:")
for meta in self.metadata:
print(f" - Type: {meta['type']}, Value: {meta['value']}")
print("Materials:")
for material in self.materials:
print(f" - ID: {material['id']}, Name: {material['name']}")
print("Objects:")
for obj in self.objects:
print(f" - Object ID: {obj['id']}")
for mesh in obj['meshes']:
print(f" - Vertices: {len(mesh['vertices'])}")
print(f" - Volumes:")
for volume in mesh['volumes']:
print(f" - Material ID: {volume['materialid']}, Triangles: {len(volume['triangles'])}")
print("Constellations:")
for const in self.constellations:
print(f" - ID: {const['id']}, Instance IDs: {const['instanceids']}")
print("Textures:")
for texture in self.textures:
print(f" - ID: {texture['id']}, Name: {texture['name']}")
# Example usage
if __name__ == "__main__":
amf = AMFFile("example.amf")
amf.read()
amf.print_properties()
amf.write("output.amf")
Notes:
- The class assumes the AMF file is a ZIP archive containing an XML file (per the standard).
- It extracts and stores key properties (units, objects, materials, constellations, textures, metadata).
- The
write
method creates a new AMF file with the same properties, maintaining ZIP compression. - Error handling is included for robustness.
- This is a simplified implementation; real-world AMF files may include complex features like curved triangles or composite materials, which would require additional parsing logic.
3. Java Class for AMF File Handling
Below is a Java class using javax.xml.parsers
for XML parsing and java.util.zip
for handling the compressed AMF file.
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class AMFFile {
private String filename;
private String units = "millimeter";
private List<Metadata> metadata = new ArrayList<>();
private List<Material> materials = new ArrayList<>();
private List<Object3D> objects = new ArrayList<>();
private List<Constellation> constellations = new ArrayList<>();
private List<Texture> textures = new ArrayList<>();
static class Metadata {
String type, value;
Metadata(String type, String value) { this.type = type; this.value = value; }
}
static class Material {
String id, name;
Material(String id, String name) { this.id = id; this.name = name; }
}
static class Vertex {
double x, y, z;
Vertex(double x, double y, double z) { this.x = x; this.y = y; this.z = z; }
}
static class Triangle {
int v1, v2, v3;
Triangle(int v1, int v2, int v3) { this.v1 = v1; this.v2 = v2; this.v3 = v3; }
}
static class Volume {
String materialId;
List<Triangle> triangles = new ArrayList<>();
Volume(String materialId) { this.materialId = materialId; }
}
static class Mesh {
List<Vertex> vertices = new ArrayList<>();
List<Volume> volumes = new ArrayList<>();
}
static class Object3D {
String id;
List<Mesh> meshes = new ArrayList<>();
Object3D(String id) { this.id = id; }
}
static class Constellation {
String id;
List<String> instanceIds = new ArrayList<>();
Constellation(String id) { this.id = id; }
}
static class Texture {
String id, name;
Texture(String id, String name) { this.id = id; this.name = name; }
}
public AMFFile(String filename) {
this.filename = filename;
}
public void read() throws Exception {
try (ZipFile zf = new ZipFile(filename)) {
ZipEntry entry = zf.entries().nextElement();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(zf.getInputStream(entry));
Element root = doc.getDocumentElement();
units = root.getAttribute("unit") != null ? root.getAttribute("unit") : "millimeter";
NodeList metaNodes = root.getElementsByTagName("metadata");
for (int i = 0; i < metaNodes.getLength(); i++) {
Element meta = (Element) metaNodes.item(i);
metadata.add(new Metadata(meta.getAttribute("type"), meta.getTextContent()));
}
NodeList matNodes = root.getElementsByTagName("material");
for (int i = 0; i < matNodes.getLength(); i++) {
Element mat = (Element) matNodes.item(i);
String name = mat.getElementsByTagName("name").item(0) != null ?
mat.getElementsByTagName("name").item(0).getTextContent() : null;
materials.add(new Material(mat.getAttribute("id"), name));
}
NodeList objNodes = root.getElementsByTagName("object");
for (int i = 0; i < objNodes.getLength(); i++) {
Element obj = (Element) objNodes.item(i);
Object3D obj3d = new Object3D(obj.getAttribute("id"));
NodeList meshNodes = obj.getElementsByTagName("mesh");
for (int j = 0; j < meshNodes.getLength(); j++) {
Element mesh = (Element) meshNodes.item(j);
Mesh m = new Mesh();
NodeList vertNodes = mesh.getElementsByTagName("vertex");
for (int k = 0; k < vertNodes.getLength(); k++) {
Element vert = (Element) vertNodes.item(k);
Element coords = (Element) vert.getElementsByTagName("coordinates").item(0);
m.vertices.add(new Vertex(
Double.parseDouble(coords.getElementsByTagName("x").item(0).getTextContent()),
Double.parseDouble(coords.getElementsByTagName("y").item(0).getTextContent()),
Double.parseDouble(coords.getElementsByTagName("z").item(0).getTextContent())
));
}
NodeList volNodes = mesh.getElementsByTagName("volume");
for (int k = 0; k < volNodes.getLength(); k++) {
Element vol = (Element) volNodes.item(k);
Volume volume = new Volume(vol.getAttribute("materialid"));
NodeList triNodes = vol.getElementsByTagName("triangle");
for (int l = 0; l < triNodes.getLength(); l++) {
Element tri = (Element) triNodes.item(l);
volume.triangles.add(new Triangle(
Integer.parseInt(tri.getElementsByTagName("v1").item(0).getTextContent()),
Integer.parseInt(tri.getElementsByTagName("v2").item(0).getTextContent()),
Integer.parseInt(tri.getElementsByTagName("v3").item(0).getTextContent())
));
}
m.volumes.add(volume);
}
obj3d.meshes.add(m);
}
objects.add(obj3d);
}
NodeList constNodes = root.getElementsByTagName("constellation");
for (int i = 0; i < constNodes.getLength(); i++) {
Element constEl = (Element) constNodes.item(i);
Constellation c = new Constellation(constEl.getAttribute("id"));
NodeList instNodes = constEl.getElementsByTagName("instance");
for (int j = 0; j < instNodes.getLength(); j++) {
c.instanceIds.add(((Element) instNodes.item(j)).getAttribute("objectid"));
}
constellations.add(c);
}
NodeList texNodes = root.getElementsByTagName("texture");
for (int i = 0; i < texNodes.getLength(); i++) {
Element tex = (Element) texNodes.item(i);
String name = tex.getElementsByTagName("name").item(0) != null ?
tex.getElementsByTagName("name").item(0).getTextContent() : null;
textures.add(new Texture(tex.getAttribute("id"), name));
}
}
}
public void write(String outputFilename) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.newDocument();
Element root = doc.createElement("amf");
root.setAttribute("unit", units);
doc.appendChild(root);
for (Metadata meta : metadata) {
Element metaEl = doc.createElement("metadata");
metaEl.setAttribute("type", meta.type);
metaEl.setTextContent(meta.value);
root.appendChild(metaEl);
}
for (Material mat : materials) {
Element matEl = doc.createElement("material");
matEl.setAttribute("id", mat.id);
if (mat.name != null) {
Element nameEl = doc.createElement("name");
nameEl.setTextContent(mat.name);
matEl.appendChild(nameEl);
}
root.appendChild(matEl);
}
for (Object3D obj : objects) {
Element objEl = doc.createElement("object");
objEl.setAttribute("id", obj.id);
for (Mesh mesh : obj.meshes) {
Element meshEl = doc.createElement("mesh");
Element vertsEl = doc.createElement("vertices");
for (Vertex v : mesh.vertices) {
Element vertEl = doc.createElement("vertex");
Element coordEl = doc.createElement("coordinates");
Element xEl = doc.createElement("x");
xEl.setTextContent(String.valueOf(v.x));
Element yEl = doc.createElement("y");
yEl.setTextContent(String.valueOf(v.y));
Element zEl = doc.createElement("z");
zEl.setTextContent(String.valueOf(v.z));
coordEl.appendChild(xEl);
coordEl.appendChild(yEl);
coordEl.appendChild(zEl);
vertEl.appendChild(coordEl);
vertsEl.appendChild(vertEl);
}
meshEl.appendChild(vertsEl);
for (Volume vol : mesh.volumes) {
Element volEl = doc.createElement("volume");
volEl.setAttribute("materialid", vol.materialId);
for (Triangle tri : vol.triangles) {
Element triEl = doc.createElement("triangle");
Element v1El = doc.createElement("v1");
v1El.setTextContent(String.valueOf(tri.v1));
Element v2El = doc.createElement("v2");
v2El.setTextContent(String.valueOf(tri.v2));
Element v3El = doc.createElement("v3");
v3El.setTextContent(String.valueOf(tri.v3));
triEl.appendChild(v1El);
triEl.appendChild(v2El);
triEl.appendChild(v3El);
volEl.appendChild(triEl);
}
meshEl.appendChild(volEl);
}
objEl.appendChild(meshEl);
}
root.appendChild(objEl);
}
for (Constellation c : constellations) {
Element constEl = doc.createElement("constellation");
constEl.setAttribute("id", c.id);
for (String instId : c.instanceIds) {
Element instEl = doc.createElement("instance");
instEl.setAttribute("objectid", instId);
constEl.appendChild(instEl);
}
root.appendChild(constEl);
}
for (Texture tex : textures) {
Element texEl = doc.createElement("texture");
texEl.setAttribute("id", tex.id);
if (tex.name != null) {
Element nameEl = doc.createElement("name");
nameEl.setTextContent(tex.name);
texEl.appendChild(nameEl);
}
root.appendChild(texEl);
}
// Write to ZIP
try (FileOutputStream fos = new FileOutputStream(outputFilename);
ZipOutputStream zos = new ZipOutputStream(fos)) {
zos.putNextEntry(new ZipEntry("model.amf"));
javax.xml.transform.TransformerFactory tf = javax.xml.transform.TransformerFactory.newInstance();
javax.xml.transform.Transformer t = tf.newTransformer();
t.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
t.transform(new javax.xml.transform.dom.DOMSource(doc),
new javax.xml.transform.stream.StreamResult(zos));
zos.closeEntry();
}
}
public void printProperties() {
System.out.println("AMF File: " + filename);
System.out.println("Units: " + units);
System.out.println("Metadata:");
for (Metadata meta : metadata) {
System.out.println(" - Type: " + meta.type + ", Value: " + meta.value);
}
System.out.println("Materials:");
for (Material mat : materials) {
System.out.println(" - ID: " + mat.id + ", Name: " + mat.name);
}
System.out.println("Objects:");
for (Object3D obj : objects) {
System.out.println(" - Object ID: " + obj.id);
for (Mesh mesh : obj.meshes) {
System.out.println(" - Vertices: " + mesh.vertices.size());
System.out.println(" - Volumes:");
for (Volume vol : mesh.volumes) {
System.out.println(" - Material ID: " + vol.materialId + ", Triangles: " + vol.triangles.size());
}
}
}
System.out.println("Constellations:");
for (Constellation c : constellations) {
System.out.println(" - ID: " + c.id + ", Instance IDs: " + c.instanceIds);
}
System.out.println("Textures:");
for (Texture tex : textures) {
System.out.println(" - ID: " + tex.id + ", Name: " + tex.name);
}
}
public static void main(String[] args) {
try {
AMFFile amf = new AMFFile("example.amf");
amf.read();
amf.printProperties();
amf.write("output.amf");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Notes:
- The class uses nested static classes to represent AMF structures (e.g.,
Vertex
,Triangle
,Volume
). - It handles ZIP decompression and XML parsing using standard Java libraries.
- The
write
method creates a new ZIP-compressed AMF file. - Simplified for core properties; advanced features like color or texture mapping require additional parsing.
4. JavaScript Class for AMF File Handling
Below is a JavaScript class using the JSZip
library for ZIP handling and the browser’s DOMParser
for XML parsing. This assumes a Node.js environment with JSZip
installed (npm install jszip
).
const JSZip = require('jszip');
const fs = require('fs');
class AMFFile {
constructor(filename) {
this.filename = filename;
this.units = 'millimeter';
this.metadata = [];
this.materials = [];
this.objects = [];
this.constellations = [];
this.textures = [];
}
async read() {
try {
const data = fs.readFileSync(this.filename);
const zip = await JSZip.loadAsync(data);
const xmlFile = Object.values(zip.files)[0];
const xmlContent = await xmlFile.async('text');
const parser = new DOMParser();
const doc = parser.parseFromString(xmlContent, 'application/xml');
const root = doc.documentElement;
this.units = root.getAttribute('unit') || 'millimeter';
for (let meta of root.getElementsByTagName('metadata')) {
this.metadata.push({
type: meta.getAttribute('type'),
value: meta.textContent
});
}
for (let mat of root.getElementsByTagName('material')) {
this.materials.push({
id: mat.getAttribute('id'),
name: mat.getElementsByTagName('name')[0]?.textContent || null
});
}
for (let obj of root.getElementsByTagName('object')) {
let objData = { id: obj.getAttribute('id'), meshes: [] };
for (let mesh of obj.getElementsByTagName('mesh')) {
let vertices = [];
for (let vert of mesh.getElementsByTagName('vertex')) {
let coords = vert.getElementsByTagName('coordinates')[0];
vertices.push({
x: parseFloat(coords.getElementsByTagName('x')[0].textContent),
y: parseFloat(coords.getElementsByTagName('y')[0].textContent),
z: parseFloat(coords.getElementsByTagName('z')[0].textContent)
});
}
let volumes = [];
for (let vol of mesh.getElementsByTagName('volume')) {
let triangles = [];
for (let tri of vol.getElementsByTagName('triangle')) {
triangles.push({
v1: parseInt(tri.getElementsByTagName('v1')[0].textContent),
v2: parseInt(tri.getElementsByTagName('v2')[0].textContent),
v3: parseInt(tri.getElementsByTagName('v3')[0].textContent)
});
}
volumes.push({
materialid: vol.getAttribute('materialid'),
triangles
});
}
objData.meshes.push({ vertices, volumes });
}
this.objects.push(objData);
}
for (let constEl of root.getElementsByTagName('constellation')) {
this.constellations.push({
id: constEl.getAttribute('id'),
instanceids: Array.from(constEl.getElementsByTagName('instance'))
.map(inst => inst.getAttribute('objectid'))
});
}
for (let tex of root.getElementsByTagName('texture')) {
this.textures.push({
id: tex.getAttribute('id'),
name: tex.getElementsByTagName('name')[0]?.textContent || null
});
}
} catch (e) {
console.error('Error reading AMF file:', e);
}
}
async write(outputFilename) {
try {
let xml = `<?xml version="1.0" encoding="UTF-8"?><amf unit="${this.units}">`;
for (let meta of this.metadata) {
xml += `<metadata type="${meta.type}">${meta.value}</metadata>`;
}
for (let mat of this.materials) {
xml += `<material id="${mat.id}">${mat.name ? `<name>${mat.name}</name>` : ''}</material>`;
}
for (let obj of this.objects) {
xml += `<object id="${obj.id}">`;
for (let mesh of obj.meshes) {
xml += `<mesh><vertices>`;
for (let vert of mesh.vertices) {
xml += `<vertex><coordinates><x>${vert.x}</x><y>${vert.y}</y><z>${vert.z}</z></coordinates></vertex>`;
}
xml += `</vertices>`;
for (let vol of mesh.volumes) {
xml += `<volume materialid="${vol.materialid}">`;
for (let tri of vol.triangles) {
xml += `<triangle><v1>${tri.v1}</v1><v2>${tri.v2}</v2><v3>${tri.v3}</v3></triangle>`;
}
xml += `</volume>`;
}
xml += `</mesh>`;
}
xml += `</object>`;
}
for (let constEl of this.constellations) {
xml += `<constellation id="${constEl.id}">`;
for (let instId of constEl.instanceids) {
xml += `<instance objectid="${instId}"/>`;
}
xml += `</constellation>`;
}
for (let tex of this.textures) {
xml += `<texture id="${tex.id}">${tex.name ? `<name>${tex.name}</name>` : ''}</texture>`;
}
xml += `</amf>`;
const zip = new JSZip();
zip.file('model.amf', xml);
const content = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
fs.writeFileSync(outputFilename, content);
} catch (e) {
console.error('Error writing AMF file:', e);
}
}
printProperties() {
console.log(`AMF File: ${this.filename}`);
console.log(`Units: ${this.units}`);
console.log('Metadata:');
this.metadata.forEach(meta => console.log(` - Type: ${meta.type}, Value: ${meta.value}`));
console.log('Materials:');
this.materials.forEach(mat => console.log(` - ID: ${mat.id}, Name: ${mat.name}`));
console.log('Objects:');
this.objects.forEach(obj => {
console.log(` - Object ID: ${obj.id}`);
obj.meshes.forEach(mesh => {
console.log(` - Vertices: ${mesh.vertices.length}`);
console.log(` - Volumes:`);
mesh.volumes.forEach(vol => {
console.log(` - Material ID: ${vol.materialid}, Triangles: ${vol.triangles.length}`);
});
});
});
console.log('Constellations:');
this.constellations.forEach(c => console.log(` - ID: ${c.id}, Instance IDs: ${c.instanceids}`));
console.log('Textures:');
this.textures.forEach(tex => console.log(` - ID: ${tex.id}, Name: ${tex.name}`));
}
}
// Example usage
(async () => {
const amf = new AMFFile('example.amf');
await amf.read();
amf.printProperties();
await amf.write('output.amf');
})();
Notes:
- Requires
JSZip
for ZIP handling in Node.js. - Uses
DOMParser
for XML parsing, suitable for both Node.js and browser environments. - Asynchronous methods handle file I/O and ZIP processing.
- Simplified for core AMF features; texture or color parsing would need extension.
5. C Class for AMF File Handling
C does not have classes, but we can use structs and functions to achieve similar functionality. Below is a C implementation using libxml2
for XML parsing and zlib
for ZIP handling (requires libxml2
and zlib
libraries).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <zlib.h>
#include <zip.h>
typedef struct {
char* type;
char* value;
} Metadata;
typedef struct {
char* id;
char* name;
} Material;
typedef struct {
double x, y, z;
} Vertex;
typedef struct {
int v1, v2, v3;
} Triangle;
typedef struct {
char* materialid;
Triangle* triangles;
int triangle_count;
} Volume;
typedef struct {
Vertex* vertices;
int vertex_count;
Volume* volumes;
int volume_count;
} Mesh;
typedef struct {
char* id;
Mesh* meshes;
int mesh_count;
} Object3D;
typedef struct {
char* id;
char** instance_ids;
int instance_count;
} Constellation;
typedef struct {
char* id;
char* name;
} Texture;
typedef struct {
char* filename;
char* units;
Metadata* metadata;
int metadata_count;
Material* materials;
int material_count;
Object3D* objects;
int object_count;
Constellation* constellations;
int constellation_count;
Texture* textures;
int texture_count;
} AMFFile;
void init_amf(AMFFile* amf, const char* filename) {
amf->filename = strdup(filename);
amf->units = strdup("millimeter");
amf->metadata = NULL;
amf->metadata_count = 0;
amf->materials = NULL;
amf->material_count = 0;
amf->objects = NULL;
amf->object_count = 0;
amf->constellations = NULL;
amf->constellation_count = 0;
amf->textures = NULL;
amf->texture_count = 0;
}
void free_amf(AMFFile* amf) {
free(amf->filename);
free(amf->units);
for (int i = 0; i < amf->metadata_count; i++) {
free(amf->metadata[i].type);
free(amf->metadata[i].value);
}
free(amf->metadata);
for (int i = 0; i < amf->material_count; i++) {
free(amf->materials[i].id);
free(amf->materials[i].name);
}
free(amf->materials);
for (int i = 0; i < amf->object_count; i++) {
for (int j = 0; j < amf->objects[i].mesh_count; j++) {
free(amf->objects[i].meshes[j].vertices);
for (int k = 0; k < amf->objects[i].meshes[j].volume_count; k++) {
free(amf->objects[i].meshes[j].volumes[k].materialid);
free(amf->objects[i].meshes[j].volumes[k].triangles);
}
free(amf->objects[i].meshes[j].volumes);
}
free(amf->objects[i].meshes);
free(amf->objects[i].id);
}
free(amf->objects);
for (int i = 0; i < amf->constellation_count; i++) {
for (int j = 0; j < amf->constellations[i].instance_count; j++) {
free(amf->constellations[i].instance_ids[j]);
}
free(amf->constellations[i].instance_ids);
free(amf->constellations[i].id);
}
free(amf->constellations);
for (int i = 0; i < amf->texture_count; i++) {
free(amf->textures[i].id);
free(amf->textures[i].name);
}
free(amf->textures);
}
int read_amf(AMFFile* amf) {
int err;
struct zip* zf = zip_open(amf->filename, 0, &err);
if (!zf) {
printf("Error opening ZIP file: %d\n", err);
return -1;
}
struct zip_file* file = zip_fopen_index(zf, 0, 0);
if (!file) {
zip_close(zf);
return -1;
}
struct zip_stat st;
zip_stat_init(&st);
zip_stat_index(zf, 0, 0, &st);
char* buffer = (char*)malloc(st.size + 1);
zip_fread(file, buffer, st.size);
buffer[st.size] = '\0';
zip_fclose(file);
zip_close(zf);
xmlDocPtr doc = xmlParseMemory(buffer, st.size);
free(buffer);
if (!doc) {
printf("Error parsing XML\n");
return -1;
}
xmlNodePtr root = xmlDocGetRootElement(doc);
if (root && xmlStrcmp(root->name, (const xmlChar*)"amf") == 0) {
const char* unit = (const char*)xmlGetProp(root, (const xmlChar*)"unit");
if (unit) {
free(amf->units);
amf->units = strdup(unit);
}
xmlNodePtr cur = root->children;
while (cur) {
if (!xmlStrcmp(cur->name, (const xmlChar*)"metadata")) {
amf->metadata = realloc(amf->metadata, (amf->metadata_count + 1) * sizeof(Metadata));
amf->metadata[amf->metadata_count].type = (char*)xmlGetProp(cur, (const xmlChar*)"type");
amf->metadata[amf->metadata_count].value = (char*)xmlNodeGetContent(cur);
amf->metadata_count++;
} else if (!xmlStrcmp(cur->name, (const xmlChar*)"material")) {
amf->materials = realloc(amf->materials, (amf->material_count + 1) * sizeof(Material));
amf->materials[amf->material_count].id = (char*)xmlGetProp(cur, (const xmlChar*)"id");
xmlNodePtr nameNode = cur->children;
while (nameNode && xmlStrcmp(nameNode->name, (const xmlChar*)"name")) nameNode = nameNode->next;
amf->materials[amf->material_count].name = nameNode ? (char*)xmlNodeGetContent(nameNode) : NULL;
amf->material_count++;
} else if (!xmlStrcmp(cur->name, (const xmlChar*)"object")) {
amf->objects = realloc(amf->objects, (amf->object_count + 1) * sizeof(Object3D));
Object3D* obj = &amf->objects[amf->object_count];
obj->id = (char*)xmlGetProp(cur, (const xmlChar*)"id");
obj->meshes = NULL;
obj->mesh_count = 0;
xmlNodePtr meshNode = cur->children;
while (meshNode) {
if (!xmlStrcmp(meshNode->name, (const xmlChar*)"mesh")) {
obj->meshes = realloc(obj->meshes, (obj->mesh_count + 1) * sizeof(Mesh));
Mesh* mesh = &obj->meshes[obj->mesh_count];
mesh->vertices = NULL;
mesh->vertex_count = 0;
mesh->volumes = NULL;
mesh->volume_count = 0;
xmlNodePtr meshChild = meshNode->children;
while (meshChild) {
if (!xmlStrcmp(meshChild->name, (const xmlChar*)"vertices")) {
xmlNodePtr vertNode = meshChild->children;
while (vertNode) {
if (!xmlStrcmp(vertNode->name, (const xmlChar*)"vertex")) {
mesh->vertices = realloc(mesh->vertices, (mesh->vertex_count + 1) * sizeof(Vertex));
xmlNodePtr coord = vertNode->children;
while (coord && xmlStrcmp(coord->name, (const xmlChar*)"coordinates")) coord = coord->next;
if (coord) {
xmlNodePtr c = coord->children;
while (c) {
if (!xmlStrcmp(c->name, (const xmlChar*)"x"))
mesh->vertices[mesh->vertex_count].x = atof((const char*)xmlNodeGetContent(c));
else if (!xmlStrcmp(c->name, (const xmlChar*)"y"))
mesh->vertices[mesh->vertex_count].y = atof((const char*)xmlNodeGetContent(c));
else if (!xmlStrcmp(c->name, (const xmlChar*)"z"))
mesh->vertices[mesh->vertex_count].z = atof((const char*)xmlNodeGetContent(c));
c = c->next;
}
mesh->vertex_count++;
}
}
vertNode = vertNode->next;
}
} else if (!xmlStrcmp(meshChild->name, (const xmlChar*)"volume")) {
mesh->volumes = realloc(mesh->volumes, (mesh->volume_count + 1) * sizeof(Volume));
Volume* vol = &mesh->volumes[mesh->volume_count];
vol->materialid = (char*)xmlGetProp(meshChild, (const xmlChar*)"materialid");
vol->triangles = NULL;
vol->triangle_count = 0;
xmlNodePtr triNode = meshChild->children;
while (triNode) {
if (!xmlStrcmp(triNode->name, (const xmlChar*)"triangle")) {
vol->triangles = realloc(vol->triangles, (vol->triangle_count + 1) * sizeof(Triangle));
xmlNodePtr t = triNode->children;
while (t) {
if (!xmlStrcmp(t->name, (const xmlChar*)"v1"))
vol->triangles[vol->triangle_count].v1 = atoi((const char*)xmlNodeGetContent(t));
else if (!xmlStrcmp(t->name, (const xmlChar*)"v2"))
vol->triangles[vol->triangle_count].v2 = atoi((const char*)xmlNodeGetContent(t));
else if (!xmlStrcmp(t->name, (const xmlChar*)"v3"))
vol->triangles[vol->triangle_count].v3 = atoi((const char*)xmlNodeGetContent(t));
t = t->next;
}
vol->triangle_count++;
}
triNode = triNode->next;
}
mesh->volume_count++;
}
meshChild = meshChild->next;
}
obj->mesh_count++;
}
meshNode = meshNode->next;
}
amf->object_count++;
} else if (!xmlStrcmp(cur->name, (const xmlChar*)"constellation")) {
amf->constellations = realloc(amf->constellations, (amf->constellation_count + 1) * sizeof(Constellation));
Constellation* c = &amf->constellations[amf->constellation_count];
c->id = (char*)xmlGetProp(cur, (const xmlChar*)"id");
c->instance_ids = NULL;
c->instance_count = 0;
xmlNodePtr instNode = cur->children;
while (instNode) {
if (!xmlStrcmp(instNode->name, (const xmlChar*)"instance")) {
c->instance_ids = realloc(c->instance_ids, (c->instance_count + 1) * sizeof(char*));
c->instance_ids[c->instance_count] = (char*)xmlGetProp(instNode, (const xmlChar*)"objectid");
c->instance_count++;
}
instNode = instNode->next;
}
amf->constellation_count++;
} else if (!xmlStrcmp(cur->name, (const xmlChar*)"texture")) {
amf->textures = realloc(amf->textures, (amf->texture_count + 1) * sizeof(Texture));
amf->textures[amf->texture_count].id = (char*)xmlGetProp(cur, (const xmlChar*)"id");
xmlNodePtr nameNode = cur->children;
while (nameNode && xmlStrcmp(nameNode->name, (const xmlChar*)"name")) nameNode = nameNode->next;
amf->textures[amf->texture_count].name = nameNode ? (char*)xmlNodeGetContent(nameNode) : NULL;
amf->texture_count++;
}
cur = cur->next;
}
}
xmlFreeDoc(doc);
return 0;
}
int write_amf(AMFFile* amf, const char* output_filename) {
xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
xmlNodePtr root = xmlNewNode(NULL, (const xmlChar*)"amf");
xmlNewProp(root, (const xmlChar*)"unit", (const xmlChar*)amf->units);
xmlDocSetRootElement(doc, root);
for (int i = 0; i < amf->metadata_count; i++) {
xmlNodePtr meta = xmlNewChild(root, NULL, (const xmlChar*)"metadata", (const xmlChar*)amf->metadata[i].value);
xmlNewProp(meta, (const xmlChar*)"type", (const xmlChar*)amf->metadata[i].type);
}
for (int i = 0; i < amf->material_count; i++) {
xmlNodePtr mat = xmlNewChild(root, NULL, (const xmlChar*)"material", NULL);
xmlNewProp(mat, (const xmlChar*)"id", (const xmlChar*)amf->materials[i].id);
if (amf->materials[i].name)
xmlNewChild(mat, NULL, (const xmlChar*)"name", (const xmlChar*)amf->materials[i].name);
}
for (int i = 0; i < amf->object_count; i++) {
xmlNodePtr obj = xmlNewChild(root, NULL, (const xmlChar*)"object", NULL);
xmlNewProp(obj, (const xmlChar*)"id", (const xmlChar*)amf->objects[i].id);
for (int j = 0; j < amf->objects[i].mesh_count; j++) {
xmlNodePtr mesh = xmlNewChild(obj, NULL, (const xmlChar*)"mesh", NULL);
xmlNodePtr vertices = xmlNewChild(mesh, NULL, (const xmlChar*)"vertices", NULL);
for (int k = 0; k < amf->objects[i].meshes[j].vertex_count; k++) {
xmlNodePtr vert = xmlNewChild(vertices, NULL, (const xmlChar*)"vertex", NULL);
xmlNodePtr coord = xmlNewChild(vert, NULL, (const xmlChar*)"coordinates", NULL);
char buf[32];
snprintf(buf, 32, "%.6f", amf->objects[i].meshes[j].vertices[k].x);
xmlNewChild(coord, NULL, (const xmlChar*)"x", (const xmlChar*)buf);
snprintf(buf, 32, "%.6f", amf->objects[i].meshes[j].vertices[k].y);
xmlNewChild(coord, NULL, (const xmlChar*)"y", (const xmlChar*)buf);
snprintf(buf, 32, "%.6f", amf->objects[i].meshes[j].vertices[k].z);
xmlNewChild(coord, NULL, (const xmlChar*)"z", (const xmlChar*)buf);
}
for (int k = 0; k < amf->objects[i].meshes[j].volume_count; k++) {
xmlNodePtr vol = xmlNewChild(mesh, NULL, (const xmlChar*)"volume", NULL);
xmlNewProp(vol, (const xmlChar*)"materialid", (const xmlChar*)amf->objects[i].meshes[j].volumes[k].materialid);
for (int l = 0; l < amf->objects[i].meshes[j].volumes[k].triangle_count; l++) {
xmlNodePtr tri = xmlNewChild(vol, NULL, (const xmlChar*)"triangle", NULL);
char buf[16];
snprintf(buf, 16, "%d", amf->objects[i].meshes[j].volumes[k].triangles[l].v1);
xmlNewChild(tri, NULL, (const xmlChar*)"v1", (const xmlChar*)buf);
snprintf(buf, 16, "%d", amf->objects[i].meshes[j].volumes[k].triangles[l].v2);
xmlNewChild(tri, NULL, (const xmlChar*)"v2", (const xmlChar*)buf);
snprintf(buf, 16, "%d", amf->objects[i].meshes[j].volumes[k].triangles[l].v3);
xmlNewChild(tri, NULL, (const xmlChar*)"v3", (const xmlChar*)buf);
}
}
}
}
for (int i = 0; i < amf->constellation_count; i++) {
xmlNodePtr c = xmlNewChild(root, NULL, (const xmlChar*)"constellation", NULL);
xmlNewProp(c, (const xmlChar*)"id", (const xmlChar*)amf->constellations[i].id);
for (int j = 0; j < amf->constellations[i].instance_count; j++) {
xmlNodePtr inst = xmlNewChild(c, NULL, (const xmlChar*)"instance", NULL);
xmlNewProp(inst, (const xmlChar*)"objectid", (const xmlChar*)amf->constellations[i].instance_ids[j]);
}
}
for (int i = 0; i < amf->texture_count; i++) {
xmlNodePtr tex = xmlNewChild(root, NULL, (const xmlChar*)"texture", NULL);
xmlNewProp(tex, (const xmlChar*)"id", (const xmlChar*)amf->textures[i].id);
if (amf->textures[i].name)
xmlNewChild(tex, NULL, (const xmlChar*)"name", (const xmlChar*)amf->textures[i].name);
}
xmlChar* xmlbuff;
int buffersize;
xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
int zip_err;
struct zip* zf = zip_open(output_filename, ZIP_CREATE | ZIP_TRUNCATE, &zip_err);
if (!zf) {
printf("Error creating ZIP file: %d\n", zip_err);
xmlFree(xmlbuff);
xmlFreeDoc(doc);
return -1;
}
struct zip_source* source = zip_source_buffer(zf, xmlbuff, buffersize, 0);
if (zip_file_add(zf, "model.amf", source, ZIP_FL_OVERWRITE) < 0) {
zip_source_free(source);
zip_close(zf);
xmlFree(xmlbuff);
xmlFreeDoc(doc);
return -1;
}
zip_close(zf);
xmlFree(xmlbuff);
xmlFreeDoc(doc);
return 0;
}
void print_amf_properties(AMFFile* amf) {
printf("AMF File: %s\n", amf->filename);
printf("Units: %s\n", amf->units);
printf("Metadata:\n");
for (int i = 0; i < amf->metadata_count; i++) {
printf(" - Type: %s, Value: %s\n", amf->metadata[i].type, amf->metadata[i].value);
}
printf("Materials:\n");
for (int i = 0; i < amf->material_count; i++) {
printf(" - ID: %s, Name: %s\n", amf->materials[i].id, amf->materials[i].name ? amf->materials[i].name : "null");
}
printf("Objects:\n");
for (int i = 0; i < amf->object_count; i++) {
printf(" - Object ID: %s\n", amf->objects[i].id);
for (int j = 0; j < amf->objects[i].mesh_count; j++) {
printf(" - Vertices: %d\n", amf->objects[i].meshes[j].vertex_count);
printf(" - Volumes:\n");
for (int k = 0; k < amf->objects[i].meshes[j].volume_count; k++) {
printf(" - Material ID: %s, Triangles: %d\n",
amf->objects[i].meshes[j].volumes[k].materialid,
amf->objects[i].meshes[j].volumes[k].triangle_count);
}
}
}
printf("Constellations:\n");
for (int i = 0; i < amf->constellation_count; i++) {
printf(" - ID: %s, Instance IDs: [", amf->constellations[i].id);
for (int j = 0; j < amf->constellations[i].instance_count; j++) {
printf("%s%s", amf->constellations[i].instance_ids[j],
j < amf->constellations[i].instance_count - 1 ? ", " : "");
}
printf("]\n");
}
printf("Textures:\n");
for (int i = 0; i < amf->texture_count; i++) {
printf(" - ID: %s, Name: %s\n", amf->textures[i].id, amf->textures[i].name ? amf->textures[i].name : "null");
}
}
int main() {
AMFFile amf;
init_amf(&amf, "example.amf");
if (read_amf(&amf) == 0) {
print_amf_properties(&amf);
write_amf(&amf, "output.amf");
}
free_amf(&amf);
return 0;
}
Notes:
- Requires
libxml2
andlibzip
libraries for XML and ZIP handling. - Uses dynamic memory allocation for flexibility; proper memory cleanup is implemented.
- Simplified parsing focuses on core AMF elements; advanced features like color or curved triangles are omitted for brevity.
- Compile with:
gcc -o amf amf.c -lxml2 -lzip -I/usr/include/libxml2
.
General Notes
- Simplifications: The implementations focus on core AMF elements (units, objects, materials, constellations, textures, metadata) as specified in ISO/ASTM 52915:2020. Advanced features like curved triangles, composite materials, or texture mapping are not fully implemented to keep the code manageable.
- Dependencies: Python requires
xml.etree.ElementTree
andzipfile
(standard libraries); Java uses standard libraries; JavaScript requiresJSZip
; C requireslibxml2
andlibzip
. - Error Handling: Each implementation includes basic error handling, but production code should include more robust validation (e.g., XML schema validation against the official XSD).
- Testing: The code assumes a valid AMF file structure. Testing with real AMF files is recommended to ensure compatibility.
- Sources: The properties and structure are based on ISO/ASTM 52915:2020 and related references.
If you need further clarification, additional features (e.g., color parsing), or help with testing, let me know!
AMF is an XML-based format for 3D printing, per ISO/ASTM 52915:2020. Files may be ZIP-compressed with .amf extension.
1. Properties Intrinsic to File System
- Extension: .amf
- Compression: Optional ZIP (single entry, .amf extension)
- Encoding: UTF-8 or UTF-16
- Signature: <?xml version="1.0" encoding="UTF-8"?>
- Version: <amf> attribute (e.g., "1.2")
- Units: <amf> attribute (millimeter|inch|foot|meter|micron; default millimeter)
- Language: <amf> xml:lang (optional)
- MIME: application/xml (inferred)
- Schema: Strict XML 1.0 adherence
- Extensibility: Backward/forward compatible
XML elements/attributes: <amf> (root), <object id> (required, <mesh><vertices><vertex><coordinates><x><y><z>, optional <normal><nx><ny><nz>), <volume materialid><triangle><v1><v2><v3>), <material id> (<color><r><g><b><a>, <composite>), <texture id width height depth tiled type> (base64 data), <constellation id><instance objectid deltax deltay deltaz rx ry rz>, <metadata type>.
- Python Code Example
import zipfile
import io
from xml.etree import ElementTree as ET
import base64
class AMFHandler:
def __init__(self):
self.tree = None
self.root = None
self.properties = {}
def load(self, filename):
with zipfile.ZipFile(filename, 'r') as zf:
xml_name = zf.namelist()[0]
xml_data = zf.read(xml_name)
self.tree = ET.parse(io.BytesIO(xml_data))
self.root = self.tree.getroot()
self._parse_properties()
def _parse_properties(self):
self.properties = {
'unit': self.root.get('unit', 'millimeter'),
'version': self.root.get('version', '1.2'),
'lang': self.root.get('{http://www.w3.org/XML/1998/namespace}lang'),
'metadata': [],
'objects': [],
'materials': [],
'textures': [],
'constellations': []
}
for elem in self.root:
if elem.tag == 'metadata':
self.properties['metadata'].append({'type': elem.get('type'), 'value': elem.text})
elif elem.tag == 'object':
obj = {'id': elem.get('id'), 'mesh': {'vertices': [], 'volumes': []}}
mesh = elem.find('mesh')
if mesh:
vertices = mesh.find('vertices')
if vertices:
for vertex in vertices:
coords = vertex.find('coordinates')
normal = vertex.find('normal')
v = {'x': float(coords.find('x').text), 'y': float(coords.find('y').text), 'z': float(coords.find('z').text)}
if normal:
v['normal'] = {'nx': float(normal.find('nx').text), 'ny': float(normal.find('ny').text), 'nz': float(normal.find('nz').text)}
obj['mesh']['vertices'].append(v)
for volume in mesh.findall('volume'):
vol = {'materialid': volume.get('materialid'), 'triangles': []}
for triangle in volume.findall('triangle'):
tri = {'v1': int(triangle.find('v1').text), 'v2': int(triangle.find('v2').text), 'v3': int(triangle.find('v3').text)}
vol['triangles'].append(tri)
obj['mesh']['volumes'].append(vol)
self.properties['objects'].append(obj)
elif elem.tag == 'material':
mat = {'id': elem.get('id'), 'colors': [], 'composites': []}
for sub in elem:
if sub.tag == 'color':
mat['colors'].append(self._parse_color(sub))
elif sub.tag == 'composite':
mat['composites'].append({'materialid': sub.get('materialid'), 'formula': sub.get('formula')})
self.properties['materials'].append(mat)
elif elem.tag == 'texture':
tex = {'id': elem.get('id'), 'width': int(elem.get('width')), 'height': int(elem.get('height')), 'depth': int(elem.get('depth', '1')),
'tiled': elem.get('tiled') == 'true', 'type': elem.get('type'), 'data': base64.b64decode(elem.text)}
self.properties['textures'].append(tex)
elif elem.tag == 'constellation':
const = {'id': elem.get('id'), 'instances': []}
for inst in elem.findall('instance'):
i = {'objectid': inst.get('objectid'), 'deltax': float(inst.find('deltax').text or '0'), 'deltay': float(inst.find('deltay').text or '0'),
'deltaz': float(inst.find('deltaz').text or '0'), 'rx': float(inst.find('rx').text or '0'), 'ry': float(inst.find('ry').text or '0'),
'rz': float(inst.find('rz').text or '0')}
const['instances'].append(i)
self.properties['constellations'].append(const)
def _parse_color(self, color_elem):
color = {'r': float(color_elem.find('r').text), 'g': float(color_elem.find('g').text), 'b': float(color_elem.find('b').text),
'a': float(color_elem.find('a').text) if color_elem.find('a') else None}
return color
def get_properties(self):
return self.properties
def write(self, filename):
root = ET.Element('amf', unit=self.properties['unit'], version=self.properties['version'])
if self.properties['lang']:
root.set('xml:lang', self.properties['lang'])
for meta in self.properties['metadata']:
ET.SubElement(root, 'metadata', type=meta['type']).text = meta['value']
for obj in self.properties['objects']:
obj_elem = ET.SubElement(root, 'object', id=obj['id'])
mesh = ET.SubElement(obj_elem, 'mesh')
vertices = ET.SubElement(mesh, 'vertices')
for v in obj['mesh']['vertices']:
vertex = ET.SubElement(vertices, 'vertex')
coords = ET.SubElement(vertex, 'coordinates')
ET.SubElement(coords, 'x').text = str(v['x'])
ET.SubElement(coords, 'y').text = str(v['y'])
ET.SubElement(coords, 'z').text = str(v['z'])
if 'normal' in v:
normal = ET.SubElement(vertex, 'normal')
ET.SubElement(normal, 'nx').text = str(v['normal']['nx'])
ET.SubElement(normal, 'ny').text = str(v['normal']['ny'])
ET.SubElement(normal, 'nz').text = str(v['normal']['nz'])
for vol in obj['mesh']['volumes']:
vol_elem = ET.SubElement(mesh, 'volume')
if vol['materialid']:
vol_elem.set('materialid', vol['materialid'])
for tri in vol['triangles']:
tri_elem = ET.SubElement(vol_elem, 'triangle')
ET.SubElement(tri_elem, 'v1').text = str(tri['v1'])
ET.SubElement(tri_elem, 'v2').text = str(tri['v2'])
ET.SubElement(tri_elem, 'v3').text = str(tri['v3'])
for mat in self.properties['materials']:
mat_elem = ET.SubElement(root, 'material', id=mat['id'])
for col in mat['colors']:
color_elem = ET.SubElement(mat_elem, 'color')
ET.SubElement(color_elem, 'r').text = str(col['r'])
ET.SubElement(color_elem, 'g').text = str(col['g'])
ET.SubElement(color_elem, 'b').text = str(col['b'])
if col['a'] is not None:
ET.SubElement(color_elem, 'a').text = str(col['a'])
for comp in mat['composites']:
ET.SubElement(mat_elem, 'composite', materialid=comp['materialid'], formula=comp['formula'])
for tex in self.properties['textures']:
tex_elem = ET.SubElement(root, 'texture', id=tex['id'], width=str(tex['width']), height=str(tex['height']), depth=str(tex['depth']),
tiled='true' if tex['tiled'] else 'false', type=tex['type'])
tex_elem.text = base64.b64encode(tex['data']).decode('utf-8')
for const in self.properties['constellations']:
const_elem = ET.SubElement(root, 'constellation', id=const['id'])
for inst in const['instances']:
inst_elem = ET.SubElement(const_elem, 'instance', objectid=inst['objectid'])
ET.SubElement(inst_elem, 'deltax').text = str(inst['deltax'])
ET.SubElement(inst_elem, 'deltay').text = str(inst['deltay'])
ET.SubElement(inst_elem, 'deltaz').text = str(inst['deltaz'])
ET.SubElement(inst_elem, 'rx').text = str(inst['rx'])
ET.SubElement(inst_elem, 'ry').text = str(inst['ry'])
ET.SubElement(inst_elem, 'rz').text = str(inst['rz'])
xml_data = ET.tostring(root, encoding='utf-8', xml_declaration=True)
with zipfile.ZipFile(filename, 'w') as zf:
zf.writestr('model.amf', xml_data)
2. Java Code Example
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.Base64;
public class AMFHandler {
private Document doc;
private Map<String, Object> properties = new HashMap<>();
public void load(String filename) throws Exception {
ZipFile zf = new ZipFile(filename);
ZipEntry entry = zf.entries().nextElement();
InputStream is = zf.getInputStream(entry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
doc = dbf.newDocumentBuilder().parse(is);
parseProperties();
}
private void parseProperties() {
Element root = doc.getDocumentElement();
properties.put("unit", root.getAttribute("unit").isEmpty() ? "millimeter" : root.getAttribute("unit"));
properties.put("version", root.getAttribute("version").isEmpty() ? "1.2" : root.getAttribute("version"));
properties.put("lang", root.getAttribute("xml:lang"));
properties.put("metadata", new ArrayList<Map<String, String>>());
properties.put("objects", new ArrayList<Map<String, Object>>());
properties.put("materials", new ArrayList<Map<String, Object>>());
properties.put("textures", new ArrayList<Map<String, Object>>());
properties.put("constellations", new ArrayList<Map<String, Object>>());
NodeList nodes = root.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
if (nodes.item(i) instanceof Element) {
Element elem = (Element) nodes.item(i);
switch (elem.getTagName()) {
case "metadata":
Map<String, String> meta = new HashMap<>();
meta.put("type", elem.getAttribute("type"));
meta.put("value", elem.getTextContent());
((List) properties.get("metadata")).add(meta);
break;
case "object":
Map<String, Object> obj = new HashMap<>();
obj.put("id", elem.getAttribute("id"));
obj.put("mesh", new HashMap<>());
Element mesh = (Element) elem.getElementsByTagName("mesh").item(0);
if (mesh != null) {
List<Map<String, Object>> vertices = new ArrayList<>();
NodeList vertexList = mesh.getElementsByTagName("vertex");
for (int j = 0; j < vertexList.getLength(); j++) {
Element vertex = (Element) vertexList.item(j);
Element coords = (Element) vertex.getElementsByTagName("coordinates").item(0);
Map<String, Object> v = new HashMap<>();
v.put("x", Float.parseFloat(coords.getElementsByTagName("x").item(0).getTextContent()));
v.put("y", Float.parseFloat(coords.getElementsByTagName("y").item(0).getTextContent()));
v.put("z", Float.parseFloat(coords.getElementsByTagName("z").item(0).getTextContent()));
Element normal = (Element) vertex.getElementsByTagName("normal").item(0);
if (normal != null) {
Map<String, Float> n = new HashMap<>();
n.put("nx", Float.parseFloat(normal.getElementsByTagName("nx").item(0).getTextContent()));
n.put("ny", Float.parseFloat(normal.getElementsByTagName("ny").item(0).getTextContent()));
n.put("nz", Float.parseFloat(normal.getElementsByTagName("nz").item(0).getTextContent()));
v.put("normal", n);
}
vertices.add(v);
}
((Map) obj.get("mesh")).put("vertices", vertices);
List<Map<String, Object>> volumes = new ArrayList<>();
NodeList volumeList = mesh.getElementsByTagName("volume");
for (int j = 0; j < volumeList.getLength(); j++) {
Element volume = (Element) volumeList.item(j);
Map<String, Object> vol = new HashMap<>();
vol.put("materialid", volume.getAttribute("materialid"));
List<Map<String, Integer>> triangles = new ArrayList<>();
NodeList triList = volume.getElementsByTagName("triangle");
for (int k = 0; k < triList.getLength(); k++) {
Element tri = (Element) triList.item(k);
Map<String, Integer> t = new HashMap<>();
t.put("v1", Integer.parseInt(tri.getElementsByTagName("v1").item(0).getTextContent()));
t.put("v2", Integer.parseInt(tri.getElementsByTagName("v2").item(0).getTextContent()));
t.put("v3", Integer.parseInt(tri.getElementsByTagName("v3").item(0).getTextContent()));
triangles.add(t);
}
vol.put("triangles", triangles);
volumes.add(vol);
}
((Map) obj.get("mesh")).put("volumes", volumes);
}
((List) properties.get("objects")).add(obj);
break;
// Add cases for material, texture, constellation similarly
}
}
}
}
public Map<String, Object> getProperties() {
return properties;
}
public void write(String filename) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document newDoc = dbf.newDocumentBuilder().newDocument();
Element root = newDoc.createElement("amf");
root.setAttribute("unit", (String) properties.get("unit"));
root.setAttribute("version", (String) properties.get("version"));
if (properties.get("lang") != null) root.setAttribute("xml:lang", (String) properties.get("lang"));
newDoc.appendChild(root);
// Build other elements from properties similarly
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
t.setOutputProperty(OutputKeys.XML_DECLARATION, "yes");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.transform(new DOMSource(newDoc), new StreamResult(baos));
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(filename))) {
zos.putNextEntry(new ZipEntry("model.amf"));
zos.write(baos.toByteArray());
zos.closeEntry();
}
}
}
3. Javascript example
const JSZip = require('jszip');
const xml2js = require('xml2js');
const builder = new xml2js.Builder();
class AMFHandler {
constructor() {
this.properties = {};
}
async load(filename) {
const fs = require('fs');
const data = fs.readFileSync(filename);
const zip = await JSZip.loadAsync(data);
const xmlName = Object.keys(zip.files)[0];
const xmlData = await zip.file(xmlName).async('string');
const result = await new Promise((resolve, reject) => xml2js.parseString(xmlData, (err, res) => err ? reject(err) : resolve(res)));
this.properties = this.parseProperties(result.amf);
}
parseProperties(root) {
return {
unit: root.$?.unit || 'millimeter',
version: root.$?.version || '1.2',
lang: root.$?.['xml:lang'],
metadata: root.metadata?.map(m => ({type: m.$?.type, value: m._})) || [],
objects: root.object?.map(o => ({
id: o.$?.id,
mesh: {
vertices: o.mesh?.[0].vertices?.[0].vertex?.map(v => {
const coords = v.coordinates[0];
const normal = v.normal?.[0];
return {
x: parseFloat(coords.x[0]),
y: parseFloat(coords.y[0]),
z: parseFloat(coords.z[0]),
normal: normal ? {nx: parseFloat(normal.nx[0]), ny: parseFloat(normal.ny[0]), nz: parseFloat(normal.nz[0])} : null
};
}) || [],
volumes: o.mesh?.[0].volume?.map(vol => ({
materialid: vol.$?.materialid,
triangles: vol.triangle?.map(tri => ({
v1: parseInt(tri.v1[0]),
v2: parseInt(tri.v2[0]),
v3: parseInt(tri.v3[0])
})) || []
})) || []
}
})) || [],
materials: root.material?.map(m => ({
id: m.$?.id,
colors: m.color?.map(c => ({
r: parseFloat(c.r[0]),
g: parseFloat(c.g[0]),
b: parseFloat(c.b[0]),
a: c.a ? parseFloat(c.a[0]) : null
})) || [],
composites: m.composite?.map(comp => ({materialid: comp.$?.materialid, formula: comp.$?.formula})) || []
})) || [],
textures: root.texture?.map(t => ({
id: t.$?.id,
width: parseInt(t.$?.width),
height: parseInt(t.$?.height),
depth: parseInt(t.$?.depth || '1'),
tiled: t.$?.tiled === 'true',
type: t.$?.type,
data: Buffer.from(t._, 'base64')
})) || [],
constellations: root.constellation?.map(c => ({
id: c.$?.id,
instances: c.instance?.map(inst => ({
objectid: inst.$?.objectid,
deltax: parseFloat(inst.deltax?.[0] || '0'),
deltay: parseFloat(inst.deltay?.[0] || '0'),
deltaz: parseFloat(inst.deltaz?.[0] || '0'),
rx: parseFloat(inst.rx?.[0] || '0'),
ry: parseFloat(inst.ry?.[0] || '0'),
rz: parseFloat(inst.rz?.[0] || '0')
})) || []
})) || []
};
}
getProperties() {
return this.properties;
}
async write(filename) {
const xmlObj = {amf: this.buildXmlObject(this.properties)};
const xmlStr = builder.buildObject(xmlObj);
const zip = new JSZip();
zip.file('model.amf', xmlStr);
const content = await zip.generateAsync({type: 'nodebuffer'});
require('fs').writeFileSync(filename, content);
}
buildXmlObject(props) {
const root = {$: {unit: props.unit, version: props.version}};
if (props.lang) root.$['xml:lang'] = props.lang;
root.metadata = props.metadata.map(m => ({$: {type: m.type}, _: m.value}));
root.object = props.objects.map(o => ({
$: {id: o.id},
mesh: {
vertices: {vertex: o.mesh.vertices.map(v => ({
coordinates: {x: v.x.toString(), y: v.y.toString(), z: v.z.toString()},
normal: v.normal ? {nx: v.normal.nx.toString(), ny: v.normal.ny.toString(), nz: v.normal.nz.toString()} : undefined
}))},
volume: o.mesh.volumes.map(vol => ({
$: vol.materialid ? {materialid: vol.materialid} : {},
triangle: vol.triangles.map(tri => ({
v1: tri.v1.toString(),
v2: tri.v2.toString(),
v3: tri.v3.toString()
}))
}))
}
}));
// Add materials, textures, constellations similarly
return root;
}
}
4. C/C++ Code Example
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <tinyxml2.h> // Assume included
#include <miniz.h> // Assume included for ZIP
struct Vertex {
float x, y, z;
float nx, ny, nz;
bool hasNormal = false;
};
struct Triangle {
int v1, v2, v3;
};
struct Volume {
std::string materialid;
std::vector<Triangle> triangles;
};
struct Mesh {
std::vector<Vertex> vertices;
std::vector<Volume> volumes;
};
struct Object {
std::string id;
Mesh mesh;
};
class AMFHandler {
public:
std::string unit = "millimeter";
std::string version = "1.2";
std::string lang;
std::vector<std::pair<std::string, std::string>> metadata;
std::vector<Object> objects;
// Add materials, textures, constellations structs and vectors
void load(const std::string& filename) {
mz_zip_archive zip;
memset(&zip, 0, sizeof(zip));
mz_zip_reader_init_file(&zip, filename.c_str(), 0);
size_t xmlSize;
void* xmlData = mz_zip_reader_extract_to_heap(&zip, 0, &xmlSize, 0);
mz_zip_reader_end(&zip);
tinyxml2::XMLDocument doc;
doc.Parse(static_cast<char*>(xmlData));
tinyxml2::XMLElement* root = doc.FirstChildElement("amf");
unit = root->Attribute("unit") ? root->Attribute("unit") : "millimeter";
version = root->Attribute("version") ? root->Attribute("version") : "1.2";
lang = root->Attribute("xml:lang") ? root->Attribute("xml:lang") : "";
for (tinyxml2::XMLElement* elem = root->FirstChildElement(); elem; elem = elem->NextSiblingElement()) {
std::string tag = elem->Name();
if (tag == "metadata") {
metadata.emplace_back(elem->Attribute("type"), elem->GetText() ? elem->GetText() : "");
} else if (tag == "object") {
Object obj;
obj.id = elem->Attribute("id");
tinyxml2::XMLElement* mesh = elem->FirstChildElement("mesh");
if (mesh) {
tinyxml2::XMLElement* vertices = mesh->FirstChildElement("vertices");
if (vertices) {
for (tinyxml2::XMLElement* vertex = vertices->FirstChildElement("vertex"); vertex; vertex = vertex->NextSiblingElement("vertex")) {
Vertex v;
tinyxml2::XMLElement* coords = vertex->FirstChildElement("coordinates");
v.x = coords->FirstChildElement("x")->FloatText();
v.y = coords->FirstChildElement("y")->FloatText();
v.z = coords->FirstChildElement("z")->FloatText();
tinyxml2::XMLElement* normal = vertex->FirstChildElement("normal");
if (normal) {
v.nx = normal->FirstChildElement("nx")->FloatText();
v.ny = normal->FirstChildElement("ny")->FloatText();
v.nz = normal->FirstChildElement("nz")->FloatText();
v.hasNormal = true;
}
obj.mesh.vertices.push_back(v);
}
}
for (tinyxml2::XMLElement* volume = mesh->FirstChildElement("volume"); volume; volume = volume->NextSiblingElement("volume")) {
Volume vol;
vol.materialid = volume->Attribute("materialid") ? volume->Attribute("materialid") : "";
for (tinyxml2::XMLElement* triangle = volume->FirstChildElement("triangle"); triangle; triangle = triangle->NextSiblingElement("triangle")) {
Triangle tri;
tri.v1 = triangle->FirstChildElement("v1")->IntText();
tri.v2 = triangle->FirstChildElement("v2")->IntText();
tri.v3 = triangle->FirstChildElement("v3")->IntText();
vol.triangles.push_back(tri);
}
obj.mesh.volumes.push_back(vol);
}
}
objects.push_back(obj);
} // Add for other tags
}
free(xmlData);
}
void write(const std::string& filename) {
tinyxml2::XMLDocument doc;
tinyxml2::XMLElement* root = doc.NewElement("amf");
root->SetAttribute("unit", unit.c_str());
root->SetAttribute("version", version.c_str());
if (!lang.empty()) root->SetAttribute("xml:lang", lang.c_str());
doc.InsertFirstChild(root);
for (const auto& m : metadata) {
tinyxml2::XMLElement* meta = doc.NewElement("metadata");
meta->SetAttribute("type", m.first.c_str());
meta->SetText(m.second.c_str());
root->InsertEndChild(meta);
}
for (const auto& obj : objects) {
tinyxml2::XMLElement* objElem = doc.NewElement("object");
objElem->SetAttribute("id", obj.id.c_str());
tinyxml2::XMLElement* mesh = doc.NewElement("mesh");
objElem->InsertEndChild(mesh);
tinyxml2::XMLElement* vertices = doc.NewElement("vertices");
mesh->InsertEndChild(vertices);
for (const auto& v : obj.mesh.vertices) {
tinyxml2::XMLElement* vertex = doc.NewElement("vertex");
tinyxml2::XMLElement* coords = doc.NewElement("coordinates");
tinyxml2::XMLElement* x = doc.NewElement("x"); x->SetText(v.x);
tinyxml2::XMLElement* y = doc.NewElement("y"); y->SetText(v.y);
tinyxml2::XMLElement* z = doc.NewElement("z"); z->SetText(v.z);
coords->InsertEndChild(x); coords->InsertEndChild(y); coords->InsertEndChild(z);
vertex->InsertEndChild(coords);
if (v.hasNormal) {
tinyxml2::XMLElement* normal = doc.NewElement("normal");
tinyxml2::XMLElement* nx = doc.NewElement("nx"); nx->SetText(v.nx);
tinyxml2::XMLElement* ny = doc.NewElement("ny"); ny->SetText(v.ny);
tinyxml2::XMLElement* nz = doc.NewElement("nz"); nz->SetText(v.nz);
normal->InsertEndChild(nx); normal->InsertEndChild(ny); normal->InsertEndChild(nz);
vertex->InsertEndChild(normal);
}
vertices->InsertEndChild(vertex);
}
for (const auto& vol : obj.mesh.volumes) {
tinyxml2::XMLElement* volElem = doc.NewElement("volume");
if (!vol.materialid.empty()) volElem->SetAttribute("materialid", vol.materialid.c_str());
for (const auto& tri : vol.triangles) {
tinyxml2::XMLElement* triElem = doc.NewElement("triangle");
tinyxml2::XMLElement* v1 = doc.NewElement("v1"); v1->SetText(tri.v1);
tinyxml2::XMLElement* v2 = doc.NewElement("v2"); v2->SetText(tri.v2);
tinyxml2::XMLElement* v3 = doc.NewElement("v3"); v3->SetText(tri.v3);
triElem->InsertEndChild(v1); triElem->InsertEndChild(v2); triElem->InsertEndChild(v3);
volElem->InsertEndChild(triElem);
}
mesh->InsertEndChild(volElem);
}
root->InsertEndChild(objElem);
}
// Add other elements
tinyxml2::XMLPrinter printer;
doc.Accept(&printer);
std::string xmlStr = printer.CStr();
mz_zip_archive zipOut;
memset(&zipOut, 0, sizeof(zipOut));
mz_zip_writer_init_file(&zipOut, filename.c_str(), 0);
mz_zip_writer_add_mem(&zipOut, "model.amf", xmlStr.data(), xmlStr.size(), MZ_DEFAULT_COMPRESSION);
mz_zip_writer_finalize_archive(&zipOut);
mz_zip_writer_end(&zipOut);
}
};