Task 006: .3mf File Format

Task 006: .3mf File Format

1. List of Properties Intrinsic to the .3MF File Format

The .3MF file format is an XML-based, ZIP-archived format compliant with Open Packaging Conventions (OPC), designed for additive manufacturing. It encapsulates 3D model data, metadata, and related assets in a structured package. Based on the specification, the following are the key properties intrinsic to the format's structure and file system:

  • File Extension: .3mf
  • MIME Type: model/3mf
  • File Signature (Magic Number): PK\x03\x04 (standard ZIP archive header, as .3MF is a ZIP-based OPC package)
  • Package Format: ZIP archive conforming to Open Packaging Conventions (OPC), ensuring interoperability and containing parts with defined relationships
  • Required Parts:
  • 3D Model part (/3dmodel.model): XML file with root element , containing resources, build instructions, and metadata
  • Package Relationships part (/_rels/.rels): Defines relationships between package parts
  • Content Types part (/[Content_Types].xml): Specifies MIME types for all parts in the package
  • Optional Parts:
  • Thumbnail part (/Metadata/thumbnail.png or similar): Package thumbnail image
  • 3D Texture parts (/3D/Textures/*): Image files for textures
  • Print Ticket part (/3D/Metadata/Model_PT.xml): Manufacturing instructions
  • Core Properties part (/metadata/core-properties.xml): OPC-defined metadata
  • Digital Signature Origin part (/metadata/signatureorigin): For digital signatures
  • Namespaces:
  • Core namespace: http://schemas.microsoft.com/3dmanufacturing/core/2015/02
  • Material namespace (if used): http://schemas.microsoft.com/3dmanufacturing/material/2015/02
  • Production namespace (if used): http://schemas.microsoft.com/3dmanufacturing/production/2015/06
  • Version: Specification version (e.g., 1.3.0), with backward compatibility requirements
  • Model Attributes (in  element):
  • unit: Enumeration specifying measurement units (millimeter, micron, centimeter, inch, foot, meter)
  • xml:lang: Language code for the model (e.g., en-US)
  • requiredextensions: Space-separated list of required extension namespaces
  • Metadata Properties (well-known names in  elements under ):
  • Title: Model title
  • Designer: Creator or designer name
  • Description: Model description
  • Copyright: Copyright information
  • LicenseTerms: Licensing terms
  • Rating: Content rating
  • CreationDate: Date of creation
  • ModificationDate: Date of last modification
  • Core Properties (OPC-defined, in core-properties.xml):
  • category: Content category
  • contentStatus: Status (e.g., Draft)
  • contentType: MIME type
  • created: Creation timestamp
  • creator: Author
  • description: Detailed description
  • identifier: Unique ID
  • keywords: Keywords
  • language: Language
  • lastModifiedBy: Last modifier
  • lastPrinted: Last print date
  • modified: Modification timestamp
  • revision: Revision number
  • subject: Subject
  • title: Title
  • version: Version number
  • Other Intrinsic Properties:
  • XML Encoding: UTF-8, with en-US locale for decimal values
  • Extension Points:  elements and  for extensibility
  • Digital Signatures: Supported via OPC, with normalization required for XML canonicalization

These properties ensure the format's integrity, interoperability, and extensibility within file systems.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .3MF Property Dump

The following is an HTML snippet with embedded JavaScript suitable for embedding in a Ghost blog post. It allows users to drag and drop a .3MF file, unzips it using JSZip (included via CDN), parses the 3dmodel.model XML, and displays the properties listed in section 1 on the screen. Include the JSZip library for ZIP handling.

Drag and drop a .3MF file here

4. Python Class for .3MF Handling

The following Python class uses standard libraries to open, decode, read, write, and print .3MF properties. The write method creates a minimal .3MF file with sample properties.

import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO

class ThreeMF:
    def __init__(self, filename=None):
        self.properties = {}
        self.model_xml = None
        self.core_xml = None
        self.ns = {'core': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02'}
        self.core_ns = {
            'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
            'dc': 'http://purl.org/dc/elements/1.1/',
            'dcterms': 'http://purl.org/dc/terms/'
        }
        if filename:
            self.read(filename)

    def read(self, filename):
        with zipfile.ZipFile(filename, 'r') as zf:
            self.model_xml = ET.fromstring(zf.read('3dmodel.model'))
            self.properties['File Extension'] = '.3mf'
            self.properties['MIME Type'] = 'model/3mf'
            self.properties['Package Format'] = 'ZIP (OPC)'
            self.properties['Model Unit'] = self.model_xml.get('unit')
            self.properties['Language'] = self.model_xml.get('{http://www.w3.org/XML/1998/namespace}lang')
            for meta in self.model_xml.findall('core:metadata', self.ns):
                name = meta.get('name')
                self.properties[name] = meta.text
            if 'metadata/core-properties.xml' in zf.namelist():
                self.core_xml = ET.fromstring(zf.read('metadata/core-properties.xml'))
                for tag, prefix_local in [('Title', 'dc:title'), ('Creator', 'dc:creator'), ('Created', 'dcterms:created'), ('Modified', 'dcterms:modified')]:
                    prefix, local = prefix_local.split(':')
                    elem = self.core_xml.find(f'{prefix}:{local}', self.core_ns)
                    if elem is not None:
                        self.properties[tag] = elem.text

    def print_properties(self):
        for key, value in self.properties.items():
            print(f'{key}: {value}')

    def write(self, filename):
        # Create minimal .3MF with sample properties
        model_root = ET.Element('model', {'unit': 'millimeter', 'xml:lang': 'en-US'}, nsmap={'': self.ns['core']})
        metadata = ET.SubElement(model_root, 'metadata', {'name': 'Title'})
        metadata.text = 'Sample Model'
        model_xml_str = ET.tostring(model_root, encoding='utf-8', xml_declaration=True)

        core_root = ET.Element('cp:coreProperties', nsmap=self.core_ns)
        ET.SubElement(core_root, 'dc:title').text = 'Sample Title'
        ET.SubElement(core_root, 'dc:creator').text = 'Sample Creator'
        core_xml_str = ET.tostring(core_root, encoding='utf-8', xml_declaration=True)

        content_types = b'<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/></Types>'
        rels = b'<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Target="/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/></Relationships>'

        with zipfile.ZipFile(filename, 'w') as zf:
            zf.writestr('3dmodel.model', model_xml_str)
            zf.writestr('metadata/core-properties.xml', core_xml_str)
            zf.writestr('[Content_Types].xml', content_types)
            zf.writestr('_rels/.rels', rels)

5. Java Class for .3MF Handling

The following Java class uses standard libraries to open, decode, read, write, and print .3MF properties. The write method creates a minimal .3MF file.

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class ThreeMF {
    private java.util.Map<String, String> properties = new java.util.HashMap<>();
    private String namespace = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02";
    private String[] coreTags = {"dc:title", "dc:creator", "dcterms:created", "dcterms:modified"};
    private String[] coreNamespaces = {"http://purl.org/dc/elements/1.1/", "http://purl.org/dc/terms/"};

    public ThreeMF(String filename) throws Exception {
        if (filename != null) read(filename);
    }

    public void read(String filename) throws Exception {
        try (ZipFile zf = new ZipFile(filename)) {
            InputStream modelIs = zf.getInputStream(zf.getEntry("3dmodel.model"));
            Document modelDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(modelIs);
            Element model = modelDoc.getDocumentElement();

            properties.put("File Extension", ".3mf");
            properties.put("MIME Type", "model/3mf");
            properties.put("Package Format", "ZIP (OPC)");
            properties.put("Model Unit", model.getAttribute("unit"));
            properties.put("Language", model.getAttribute("xml:lang"));

            NodeList metas = model.getElementsByTagNameNS(namespace, "metadata");
            for (int i = 0; i < metas.getLength(); i++) {
                Element meta = (Element) metas.item(i);
                properties.put(meta.getAttribute("name"), meta.getTextContent());
            }

            ZipEntry coreEntry = zf.getEntry("metadata/core-properties.xml");
            if (coreEntry != null) {
                InputStream coreIs = zf.getInputStream(coreEntry);
                Document coreDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(coreIs);
                for (String tag : coreTags) {
                    String[] parts = tag.split(":");
                    String ns = parts[0].equals("dc") ? coreNamespaces[0] : coreNamespaces[1];
                    NodeList elems = coreDoc.getElementsByTagNameNS(ns, parts[1]);
                    if (elems.getLength() > 0) {
                        properties.put(tag, elems.item(0).getTextContent());
                    }
                }
            }
        }
    }

    public void printProperties() {
        for (java.util.Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String filename) throws Exception {
        // Create minimal .3MF
        String modelXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><model unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\"><metadata name=\"Title\">Sample Model</metadata></model>";
        String coreXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\"><dc:title>Sample Title</dc:title><dc:creator>Sample Creator</dc:creator></cp:coreProperties>";
        String contentTypes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/></Types>";
        String rels = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Target=\"/3dmodel.model\" Id=\"rel0\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\"/></Relationships>";

        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(filename))) {
            zos.putNextEntry(new ZipEntry("3dmodel.model"));
            zos.write(modelXml.getBytes("UTF-8"));
            zos.closeEntry();
            zos.putNextEntry(new ZipEntry("metadata/core-properties.xml"));
            zos.write(coreXml.getBytes("UTF-8"));
            zos.closeEntry();
            zos.putNextEntry(new ZipEntry("[Content_Types].xml"));
            zos.write(contentTypes.getBytes("UTF-8"));
            zos.closeEntry();
            zos.putNextEntry(new ZipEntry("_rels/.rels"));
            zos.write(rels.getBytes("UTF-8"));
            zos.closeEntry();
        }
    }
}

6. JavaScript Class for .3MF Handling

The following JavaScript class (for browser environments) uses JSZip for ZIP handling and DOMParser for XML. It can open (via ArrayBuffer), decode, read, write (generate blob), and print properties to console.

class ThreeMF {
  constructor(fileBuffer = null) {
    this.properties = {};
    this.ns = 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02';
    this.coreNs = {
      dc: 'http://purl.org/dc/elements/1.1/',
      dcterms: 'http://purl.org/dc/terms/',
      cp: 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'
    };
    if (fileBuffer) this.read(fileBuffer);
  }

  async read(fileBuffer) {
    const zip = await JSZip.loadAsync(fileBuffer);
    const modelXml = await zip.file('3dmodel.model').async('string');
    const parser = new DOMParser();
    const modelDoc = parser.parseFromString(modelXml, 'application/xml');
    const model = modelDoc.getElementsByTagName('model')[0];

    this.properties['File Extension'] = '.3mf';
    this.properties['MIME Type'] = 'model/3mf';
    this.properties['Package Format'] = 'ZIP (OPC)';
    this.properties['Model Unit'] = model.getAttribute('unit');
    this.properties['Language'] = model.getAttribute('xml:lang');

    const metas = modelDoc.getElementsByTagNameNS(this.ns, 'metadata');
    for (let meta of metas) {
      const name = meta.getAttribute('name');
      this.properties[name] = meta.textContent;
    }

    if (zip.files['metadata/core-properties.xml']) {
      const coreXml = await zip.file('metadata/core-properties.xml').async('string');
      const coreDoc = parser.parseFromString(coreXml, 'application/xml');
      ['dc:title', 'dc:creator', 'dcterms:created', 'dcterms:modified'].forEach(tag => {
        const [prefix, local] = tag.split(':');
        const elems = coreDoc.getElementsByTagNameNS(this.coreNs[prefix], local);
        if (elems.length > 0) this.properties[tag] = elems[0].textContent;
      });
    }
  }

  printProperties() {
    for (const [key, value] of Object.entries(this.properties)) {
      console.log(`${key}: ${value}`);
    }
  }

  async write() {
    const zip = new JSZip();
    const modelXml = `<?xml version="1.0" encoding="UTF-8"?><model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02"><metadata name="Title">Sample Model</metadata></model>`;
    const coreXml = `<?xml version="1.0" encoding="UTF-8"?><cp:coreProperties xmlns:cp="${this.coreNs.cp}" xmlns:dc="${this.coreNs.dc}" xmlns:dcterms="${this.coreNs.dcterms}"><dc:title>Sample Title</dc:title><dc:creator>Sample Creator</dc:creator></cp:coreProperties>`;
    const contentTypes = `<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/></Types>`;
    const rels = `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Target="/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/></Relationships>`;

    zip.file('3dmodel.model', modelXml);
    zip.file('metadata/core-properties.xml', coreXml);
    zip.file('[Content_Types].xml', contentTypes);
    zip.file('_rels/.rels', rels);

    return await zip.generateAsync({type: 'blob'});
  }
}

7. C++ Class for .3MF Handling

The following C++ class assumes use of libzip for ZIP handling and tinyxml2 for XML parsing (external libraries required; include via #include <zip.h> and #include <tinyxml2.h>). It opens, decodes, reads, writes, and prints properties to console. Compile with appropriate library links.

#include <iostream>
#include <map>
#include <string>
#include <zip.h>
#include <tinyxml2.h>

class ThreeMF {
private:
    std::map<std::string, std::string> properties;
    const char* ns = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02";

public:
    ThreeMF(const char* filename = nullptr) {
        if (filename) read(filename);
    }

    void read(const char* filename) {
        zip_t* za = zip_open(filename, ZIP_RDONLY, nullptr);
        if (!za) return;

        zip_file_t* modelFile = zip_fopen(za, "3dmodel.model", 0);
        if (modelFile) {
            std::string modelXml;
            char buf[1024];
            zip_int64_t len;
            while ((len = zip_fread(modelFile, buf, sizeof(buf))) > 0) modelXml.append(buf, len);
            zip_fclose(modelFile);

            tinyxml2::XMLDocument modelDoc;
            modelDoc.Parse(modelXml.c_str());
            tinyxml2::XMLElement* model = modelDoc.FirstChildElement("model");

            if (model) {
                properties["File Extension"] = ".3mf";
                properties["MIME Type"] = "model/3mf";
                properties["Package Format"] = "ZIP (OPC)";
                properties["Model Unit"] = model->Attribute("unit");
                properties["Language"] = model->Attribute("xml:lang");

                for (tinyxml2::XMLElement* meta = model->FirstChildElement("metadata"); meta; meta = meta->NextSiblingElement("metadata")) {
                    properties[meta->Attribute("name")] = meta->GetText() ? meta->GetText() : "";
                }
            }
        }

        zip_file_t* coreFile = zip_fopen(za, "metadata/core-properties.xml", 0);
        if (coreFile) {
            std::string coreXml;
            char buf[1024];
            zip_int64_t len;
            while ((len = zip_fread(coreFile, buf, sizeof(buf))) > 0) coreXml.append(buf, len);
            zip_fclose(coreFile);

            tinyxml2::XMLDocument coreDoc;
            coreDoc.Parse(coreXml.c_str());
            tinyxml2::XMLElement* core = coreDoc.FirstChildElement("cp:coreProperties");

            if (core) {
                const char* tags[] = {"dc:title", "dc:creator", "dcterms:created", "dcterms:modified"};
                for (const char* tag : tags) {
                    tinyxml2::XMLElement* elem = core->FirstChildElement(tag);
                    if (elem) properties[tag] = elem->GetText() ? elem->GetText() : "";
                }
            }
        }

        zip_close(za);
    }

    void printProperties() {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const char* filename) {
        zip_t* za = zip_open(filename, ZIP_CREATE | ZIP_TRUNCATE, nullptr);
        if (!za) return;

        const char* modelXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><model unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\"><metadata name=\"Title\">Sample Model</metadata></model>";
        zip_source_t* src = zip_source_buffer(za, modelXml, strlen(modelXml), 0);
        zip_file_add(za, "3dmodel.model", src, ZIP_FL_ENC_UTF_8);

        const char* coreXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\"><dc:title>Sample Title</dc:title><dc:creator>Sample Creator</dc:creator></cp:coreProperties>";
        src = zip_source_buffer(za, coreXml, strlen(coreXml), 0);
        zip_file_add(za, "metadata/core-properties.xml", src, ZIP_FL_ENC_UTF_8);

        const char* contentTypes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/></Types>";
        src = zip_source_buffer(za, contentTypes, strlen(contentTypes), 0);
        zip_file_add(za, "[Content_Types].xml", src, ZIP_FL_ENC_UTF_8);

        const char* rels = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Target=\"/3dmodel.model\" Id=\"rel0\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\"/></Relationships>";
        src = zip_source_buffer(za, rels, strlen(rels), 0);
        zip_file_add(za, "_rels/.rels", src, ZIP_FL_ENC_UTF_8);

        zip_close(za);
    }
};