Task 006: .3mf File Format

Task 006: .3mf File Format

The 3D Manufacturing Format (3MF) is an XML-based file format designed for additive manufacturing, developed by the 3MF Consortium and standardized as ISO/IEC 25422:2025. It addresses limitations of older formats like STL by supporting geometry, materials, colors, textures, and metadata in a single ZIP-compressed archive. Below, I’ll address each task based on the 3MF Core Specification v1.3.0 and related extensions, focusing on properties intrinsic to the file system and providing code to handle these properties.


1. Properties of the .3MF File Format Intrinsic to Its File System

The 3MF file format is built on the Open Packaging Conventions (OPC), using a ZIP archive to store XML-based parts and relationships. The following properties are intrinsic to the 3MF file system, as defined by the Core Specification and extensions:

  • File Extension: .3mf
  • MIME Type: application/vnd.ms-package.3dmanufacturing-3dmodel+xml, model/3mf
  • Container Format: ZIP archive, adhering to OPC
  • Core Structure:
  • 3D Payload: Contains one or more 3D model definitions (XML-based, typically in /3D/3dmodel.model).
  • Relationships File: _rels/.rels defines relationships between parts in the OPC package.
  • Content Types File: [Content_Types].xml specifies MIME types for all parts in the package.
  • Mandatory Parts:
  • 3D Model Part: The primary XML file (/3D/3dmodel.model) containing model geometry, materials, and build instructions.
  • Relationships Part: Defines connections between parts (e.g., /3D/_rels/3dmodel.model.rels).
  • Optional Parts:
  • Thumbnail: A .jpg or .png image (e.g., /Metadata/thumbnail.png) for visual preview.
  • Print Ticket: XML file with print settings (e.g., /3D/PrintTicket.xml).
  • Digital Signatures: For authenticity and integrity (e.g., /Metadata/DigitalSignature/).
  • Custom Metadata: Key-value pairs for author, license, or production notes (in /Metadata/ or model XML).
  • Extensions (optional, as per 3MF extensions):
  • Materials and Properties: Defines multi-material and color data.
  • Beam Lattice: Supports lattice structures with nodes and beams.
  • Slice Extension: Includes 2D sliced model data.
  • Secure Content: Encryption mechanisms for content protection.
  • Displacement: Texture-based mesh surface modifications.
  • Boolean Operations: Shape modification data.
  • Volumetric: Voxel or implicit data support.
  • File System Characteristics:
  • Compression: ZIP-based compression for efficient storage.
  • Extensibility: Supports custom parts via XML namespaces and relationships.
  • Interoperability: Standardized XML schema for cross-platform compatibility.
  • Encoding: XML parts use UTF-8 encoding.
  • Hierarchical Structure: Organized as a package with interdependent parts and relationships.

These properties are derived from the 3MF Core Specification and extensions available at 3mf.io and GitHub repositories like 3MFConsortium/spec_core.


2. Python Class for .3MF File Handling

Below is a Python class that opens, reads, writes, and prints the intrinsic properties of a .3MF file. It uses the zipfile module for ZIP handling and xml.etree.ElementTree for XML parsing.

import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path
import os

class ThreeMFFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            "file_extension": ".3mf",
            "mime_type": [],
            "container_format": "ZIP",
            "model_part": None,
            "relationships_file": None,
            "content_types_file": None,
            "thumbnail": None,
            "print_ticket": None,
            "digital_signatures": None,
            "custom_metadata": [],
            "extensions": []
        }

    def read_3mf(self):
        """Read and decode the .3MF file properties."""
        try:
            with zipfile.ZipFile(self.filepath, 'r') as zf:
                # Check content types
                if '[Content_Types].xml' in zf.namelist():
                    with zf.open('[Content_Types].xml') as ct_file:
                        content_types = ET.parse(ct_file).getroot()
                        self.properties["mime_type"] = [
                            override.attrib.get("ContentType")
                            for override in content_types.findall("{http://schemas.openxmlformats.org/package/2006/content-types}Override")
                        ]

                # Check relationships
                if '_rels/.rels' in zf.namelist():
                    self.properties["relationships_file"] = "_rels/.rels"

                # Check 3D model part
                if '3D/3dmodel.model' in zf.namelist():
                    with zf.open('3D/3dmodel.model') as model_file:
                        model = ET.parse(model_file).getroot()
                        self.properties["model_part"] = "3D/3dmodel.model"
                        # Extract metadata
                        metadata = model.findall("{http://schemas.openxmlformats.org/3dmanufacturing/core/2015/02}metadata")
                        self.properties["custom_metadata"] = [
                            {m.attrib["name"]: m.text} for m in metadata
                        ]

                # Check optional parts
                for file in zf.namelist():
                    if file.endswith(('.jpg', '.png')) and 'thumbnail' in file.lower():
                        self.properties["thumbnail"] = file
                    if 'PrintTicket.xml' in file:
                        self.properties["print_ticket"] = file
                    if 'DigitalSignature' in file:
                        self.properties["digital_signatures"] = file

                # Detect extensions (simplified check via namespaces)
                if '3D/3dmodel.model' in zf.namelist():
                    with zf.open('3D/3dmodel.model') as model_file:
                        model = ET.parse(model_file).getroot()
                        for prefix, uri in model.nsmap.items():
                            if uri.startswith("http://schemas.3mf.io/"):
                                self.properties["extensions"].append(uri)

        except Exception as e:
            print(f"Error reading .3MF file: {e}")

    def write_3mf(self, output_path):
        """Write a new .3MF file with the current properties."""
        try:
            with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
                # Write a minimal 3D model part
                model_xml = ET.Element("model", xmlns="http://schemas.openxmlformats.org/3dmanufacturing/core/2015/02")
                for meta in self.properties["custom_metadata"]:
                    for name, value in meta.items():
                        ET.SubElement(model_xml, "metadata", name=name).text = value
                zf.writestr("3D/3dmodel.model", ET.tostring(model_xml, encoding='utf-8'))

                # Write content types
                content_types = ET.Element("Types", xmlns="http://schemas.openxmlformats.org/package/2006/content-types")
                ET.SubElement(content_types, "Default", Extension="model", ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml")
                zf.writestr("[Content_Types].xml", ET.tostring(content_types, encoding='utf-8'))

                # Write relationships
                rels = ET.Element("Relationships", xmlns="http://schemas.openxmlformats.org/package/2006/relationships")
                ET.SubElement(rels, "Relationship", Target="/3D/3dmodel.model", Id="rel0", Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")
                zf.writestr("_rels/.rels", ET.tostring(rels, encoding='utf-8'))

                # Write thumbnail if exists
                if self.properties["thumbnail"]:
                    with zipfile.ZipFile(self.filepath, 'r') as zf_in:
                        if self.properties["thumbnail"] in zf_in.namelist():
                            zf.writestr(self.properties["thumbnail"], zf_in.read(self.properties["thumbnail"]))

        except Exception as e:
            print(f"Error writing .3MF file: {e}")

    def print_properties(self):
        """Print all intrinsic properties to console."""
        for key, value in self.properties.items():
            print(f"{key}: {value}")

# Example usage
if __name__ == "__main__":
    filepath = "example.3mf"
    three_mf = ThreeMFFile(filepath)
    three_mf.read_3mf()
    three_mf.print_properties()
    three_mf.write_3mf("output.3mf")

This class reads a .3MF file, extracts its properties (e.g., model part, relationships, metadata), and writes a minimal .3MF file with the same properties. It assumes the input file follows the 3MF structure. Run it with a valid .3MF file to test.


3. Java Class for .3MF File Handling

Below is a Java class using java.util.zip for ZIP handling and javax.xml.parsers for XML parsing.

import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

public class ThreeMFFile {
    private String filepath;
    private Map<String, Object> properties;

    public ThreeMFFile(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        properties.put("file_extension", ".3mf");
        properties.put("mime_type", new ArrayList<String>());
        properties.put("container_format", "ZIP");
        properties.put("model_part", null);
        properties.put("relationships_file", null);
        properties.put("content_types_file", null);
        properties.put("thumbnail", null);
        properties.put("print_ticket", null);
        properties.put("digital_signatures", null);
        properties.put("custom_metadata", new ArrayList<Map<String, String>>());
        properties.put("extensions", new ArrayList<String>());
    }

    public void read3MF() {
        try (ZipFile zf = new ZipFile(filepath)) {
            // Check content types
            ZipEntry contentTypes = zf.getEntry("[Content_Types].xml");
            if (contentTypes != null) {
                Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                        .parse(zf.getInputStream(contentTypes));
                NodeList overrides = doc.getElementsByTagName("Override");
                List<String> mimeTypes = new ArrayList<>();
                for (int i = 0; i < overrides.getLength(); i++) {
                    mimeTypes.add(overrides.item(i).getAttributes().getNamedItem("ContentType").getNodeValue());
                }
                properties.put("mime_type", mimeTypes);
                properties.put("content_types_file", "[Content_Types].xml");
            }

            // Check relationships
            if (zf.getEntry("_rels/.rels") != null) {
                properties.put("relationships_file", "_rels/.rels");
            }

            // Check 3D model part
            ZipEntry modelEntry = zf.getEntry("3D/3dmodel.model");
            if (modelEntry != null) {
                Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                        .parse(zf.getInputStream(modelEntry));
                properties.put("model_part", "3D/3dmodel.model");
                NodeList metadata = doc.getElementsByTagName("metadata");
                List<Map<String, String>> metaList = new ArrayList<>();
                for (int i = 0; i < metadata.getLength(); i++) {
                    NamedNodeMap attrs = metadata.item(i).getAttributes();
                    Map<String, String> meta = new HashMap<>();
                    meta.put(attrs.getNamedItem("name").getNodeValue(), metadata.item(i).getTextContent());
                    metaList.add(meta);
                }
                properties.put("custom_metadata", metaList);
            }

            // Check optional parts
            Enumeration<? extends ZipEntry> entries = zf.entries();
            while (entries.hasMoreElements()) {
                String name = entries.nextElement().getName();
                if (name.endsWith(".jpg") || name.endsWith(".png") && name.toLowerCase().contains("thumbnail")) {
                    properties.put("thumbnail", name);
                }
                if (name.contains("PrintTicket.xml")) {
                    properties.put("print_ticket", name);
                }
                if (name.contains("DigitalSignature")) {
                    properties.put("digital_signatures", name);
                }
            }

            // Detect extensions
            if (modelEntry != null) {
                Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                        .parse(zf.getInputStream(modelEntry));
                NamedNodeMap ns = doc.getDocumentElement().getAttributes();
                List<String> extensions = new ArrayList<>();
                for (int i = 0; i < ns.getLength(); i++) {
                    String uri = ns.item(i).getNodeValue();
                    if (uri.startsWith("http://schemas.3mf.io/")) {
                        extensions.add(uri);
                    }
                }
                properties.put("extensions", extensions);
            }

        } catch (Exception e) {
            System.out.println("Error reading .3MF file: " + e.getMessage());
        }
    }

    public void write3MF(String outputPath) {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputPath))) {
            // Write 3D model part
            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document doc = db.newDocument();
            Element model = doc.createElementNS("http://schemas.openxmlformats.org/3dmanufacturing/core/2015/02", "model");
            doc.appendChild(model);
            for (Map<String, String> meta : (List<Map<String, String>>) properties.get("custom_metadata")) {
                for (Map.Entry<String, String> entry : meta.entrySet()) {
                    Element metaEl = doc.createElement("metadata");
                    metaEl.setAttribute("name", entry.getKey());
                    metaEl.setTextContent(entry.getValue());
                    model.appendChild(metaEl);
                }
            }
            zos.putNextEntry(new ZipEntry("3D/3dmodel.model"));
            Transformer t = TransformerFactory.newInstance().newTransformer();
            t.transform(new DOMSource(doc), new StreamResult(zos));
            zos.closeEntry();

            // Write content types
            doc = db.newDocument();
            Element types = doc.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Types");
            doc.appendChild(types);
            Element def = doc.createElement("Default");
            def.setAttribute("Extension", "model");
            def.setAttribute("ContentType", "application/vnd.ms-package.3dmanufacturing-3dmodel+xml");
            types.appendChild(def);
            zos.putNextEntry(new ZipEntry("[Content_Types].xml"));
            t.transform(new DOMSource(doc), new StreamResult(zos));
            zos.closeEntry();

            // Write relationships
            doc = db.newDocument();
            Element rels = doc.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationships");
            doc.appendChild(rels);
            Element rel = doc.createElement("Relationship");
            rel.setAttribute("Target", "/3D/3dmodel.model");
            rel.setAttribute("Id", "rel0");
            rel.setAttribute("Type", "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel");
            rels.appendChild(rel);
            zos.putNextEntry(new ZipEntry("_rels/.rels"));
            t.transform(new DOMSource(doc), new StreamResult(zos));
            zos.closeEntry();

            // Copy thumbnail if exists
            if (properties.get("thumbnail") != null) {
                try (ZipFile zf = new ZipFile(filepath)) {
                    ZipEntry thumbnail = zf.getEntry((String) properties.get("thumbnail"));
                    if (thumbnail != null) {
                        zos.putNextEntry(new ZipEntry((String) properties.get("thumbnail")));
                        zos.write(zf.getInputStream(thumbnail).readAllBytes());
                        zos.closeEntry();
                    }
                }
            }

        } catch (Exception e) {
            System.out.println("Error writing .3MF file: " + e.getMessage());
        }
    }

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

    public static void main(String[] args) {
        ThreeMFFile threeMF = new ThreeMFFile("example.3mf");
        threeMF.read3MF();
        threeMF.printProperties();
        threeMF.write3MF("output.3mf");
    }
}

This Java class performs similar operations to the Python class, handling the ZIP structure and XML parsing to read and write .3MF properties.


4. JavaScript Class for .3MF File Handling

Below is a JavaScript class using JSZip for ZIP handling and the DOM API for XML parsing. It’s designed for Node.js with the jszip and fs modules.

const JSZip = require('jszip');
const fs = require('fs').promises;
const { DOMParser, XMLSerializer } = require('xmldom');

class ThreeMFFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {
            file_extension: '.3mf',
            mime_type: [],
            container_format: 'ZIP',
            model_part: null,
            relationships_file: null,
            content_types_file: null,
            thumbnail: null,
            print_ticket: null,
            digital_signatures: null,
            custom_metadata: [],
            extensions: []
        };
    }

    async read3MF() {
        try {
            const data = await fs.readFile(this.filepath);
            const zip = await JSZip.loadAsync(data);

            // Check content types
            if (zip.file('[Content_Types].xml')) {
                const contentTypes = await zip.file('[Content_Types].xml').async('string');
                const doc = new DOMParser().parseFromString(contentTypes, 'text/xml');
                const overrides = doc.getElementsByTagName('Override');
                this.properties.mime_type = Array.from(overrides).map(o => o.getAttribute('ContentType'));
                this.properties.content_types_file = '[Content_Types].xml';
            }

            // Check relationships
            if (zip.file('_rels/.rels')) {
                this.properties.relationships_file = '_rels/.rels';
            }

            // Check 3D model part
            if (zip.file('3D/3dmodel.model')) {
                const modelData = await zip.file('3D/3dmodel.model').async('string');
                const doc = new DOMParser().parseFromString(modelData, 'text/xml');
                this.properties.model_part = '3D/3dmodel.model';
                const metadata = doc.getElementsByTagName('metadata');
                this.properties.custom_metadata = Array.from(metadata).map(m => ({
                    [m.getAttribute('name')]: m.textContent
                }));
                // Detect extensions
                const namespaces = doc.documentElement.attributes;
                for (let i = 0; i < namespaces.length; i++) {
                    const uri = namespaces[i].value;
                    if (uri.startsWith('http://schemas.3mf.io/')) {
                        this.properties.extensions.push(uri);
                    }
                }
            }

            // Check optional parts
            for (const file in zip.files) {
                if (file.match(/\.(jpg|png)$/i) && file.toLowerCase().includes('thumbnail')) {
                    this.properties.thumbnail = file;
                }
                if (file.includes('PrintTicket.xml')) {
                    this.properties.print_ticket = file;
                }
                if (file.includes('DigitalSignature')) {
                    this.properties.digital_signatures = file;
                }
            }

        } catch (e) {
            console.error(`Error reading .3MF file: ${e.message}`);
        }
    }

    async write3MF(outputPath) {
        try {
            const zip = new JSZip();
            // Write 3D model part
            const doc = new DOMParser().parseFromString(
                '<model xmlns="http://schemas.openxmlformats.org/3dmanufacturing/core/2015/02"></model>',
                'text/xml'
            );
            for (const meta of this.properties.custom_metadata) {
                for (const [name, value] of Object.entries(meta)) {
                    const metaEl = doc.createElement('metadata');
                    metaEl.setAttribute('name', name);
                    metaEl.textContent = value;
                    doc.documentElement.appendChild(metaEl);
                }
            }
            zip.file('3D/3dmodel.model', new XMLSerializer().serializeToString(doc));

            // Write content types
            const contentTypes = new DOMParser().parseFromString(
                '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
                'text/xml'
            );
            const def = contentTypes.createElement('Default');
            def.setAttribute('Extension', 'model');
            def.setAttribute('ContentType', 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml');
            contentTypes.documentElement.appendChild(def);
            zip.file('[Content_Types].xml', new XMLSerializer().serializeToString(contentTypes));

            // Write relationships
            const rels = new DOMParser().parseFromString(
                '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
                'text/xml'
            );
            const rel = rels.createElement('Relationship');
            rel.setAttribute('Target', '/3D/3dmodel.model');
            rel.setAttribute('Id', 'rel0');
            rel.setAttribute('Type', 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel');
            rels.documentElement.appendChild(rel);
            zip.file('_rels/.rels', new XMLSerializer().serializeToString(rels));

            // Copy thumbnail if exists
            if (this.properties.thumbnail) {
                const inputZip = await JSZip.loadAsync(await fs.readFile(this.filepath));
                if (inputZip.file(this.properties.thumbnail)) {
                    zip.file(this.properties.thumbnail, await inputZip.file(this.properties.thumbnail).async('nodebuffer'));
                }
            }

            // Write to file
            const content = await zip.generateAsync({ type: 'nodebuffer' });
            await fs.writeFile(outputPath, content);

        } catch (e) {
            console.error(`Error writing .3MF file: ${e.message}`);
        }
    }

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

// Example usage
(async () => {
    const threeMF = new ThreeMFFile('example.3mf');
    await threeMF.read3MF();
    threeMF.printProperties();
    await threeMF.write3MF('output.3mf');
})();

This requires npm install jszip xmldom for Node.js. It handles asynchronous ZIP and XML operations, reading and writing .3MF properties.


5. C Class for .3MF File Handling

C does not have a class construct, so I’ll provide a C implementation using structs and functions, leveraging libzip for ZIP handling and libxml2 for XML parsing.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

typedef struct {
    const char* filepath;
    char* file_extension;
    char** mime_type;
    int mime_type_count;
    char* container_format;
    char* model_part;
    char* relationships_file;
    char* content_types_file;
    char* thumbnail;
    char* print_ticket;
    char* digital_signatures;
    char** custom_metadata;
    int metadata_count;
    char** extensions;
    int extensions_count;
} ThreeMFFile;

ThreeMFFile* ThreeMFFile_new(const char* filepath) {
    ThreeMFFile* tmf = (ThreeMFFile*)malloc(sizeof(ThreeMFFile));
    tmf->filepath = filepath;
    tmf->file_extension = strdup(".3mf");
    tmf->mime_type = NULL;
    tmf->mime_type_count = 0;
    tmf->container_format = strdup("ZIP");
    tmf->model_part = NULL;
    tmf->relationships_file = NULL;
    tmf->content_types_file = NULL;
    tmf->thumbnail = NULL;
    tmf->print_ticket = NULL;
    tmf->digital_signatures = NULL;
    tmf->custom_metadata = NULL;
    tmf->metadata_count = 0;
    tmf->extensions = NULL;
    tmf->extensions_count = 0;
    return tmf;
}

void ThreeMFFile_free(ThreeMFFile* tmf) {
    if (tmf->file_extension) free(tmf->file_extension);
    if (tmf->container_format) free(tmf->container_format);
    if (tmf->model_part) free(tmf->model_part);
    if (tmf->relationships_file) free(tmf->relationships_file);
    if (tmf->content_types_file) free(tmf->content_types_file);
    if (tmf->thumbnail) free(tmf->thumbnail);
    if (tmf->print_ticket) free(tmf->print_ticket);
    if (tmf->digital_signatures) free(tmf->digital_signatures);
    for (int i = 0; i < tmf->mime_type_count; i++) free(tmf->mime_type[i]);
    free(tmf->mime_type);
    for (int i = 0; i < tmf->metadata_count; i++) free(tmf->custom_metadata[i]);
    free(tmf->custom_metadata);
    for (int i = 0; i < tmf->extensions_count; i++) free(tmf->extensions[i]);
    free(tmf->extensions);
    free(tmf);
}

int ThreeMFFile_read(ThreeMFFile* tmf) {
    int err;
    zip_t* zf = zip_open(tmf->filepath, ZIP_RDONLY, &err);
    if (!zf) {
        printf("Error opening .3MF file: %d\n", err);
        return -1;
    }

    // Check content types
    zip_stat_t sb;
    if (zip_stat(zf, "[Content_Types].xml", 0, &sb) == 0) {
        zip_file_t* f = zip_fopen(zf, "[Content_Types].xml", 0);
        char* contents = (char*)malloc(sb.size + 1);
        zip_fread(f, contents, sb.size);
        contents[sb.size] = '\0';
        xmlDocPtr doc = xmlReadMemory(contents, sb.size, NULL, NULL, 0);
        xmlNodePtr root = xmlDocGetRootElement(doc);
        tmf->content_types_file = strdup("[Content_Types].xml");
        tmf->mime_type = (char**)malloc(10 * sizeof(char*));
        int i = 0;
        for (xmlNodePtr node = root->children; node; node = node->next) {
            if (xmlStrcmp(node->name, (const xmlChar*)"Override") == 0) {
                xmlChar* contentType = xmlGetProp(node, (const xmlChar*)"ContentType");
                tmf->mime_type[i++] = strdup((const char*)contentType);
                xmlFree(contentType);
            }
        }
        tmf->mime_type_count = i;
        free(contents);
        xmlFreeDoc(doc);
        zip_fclose(f);
    }

    // Check relationships
    if (zip_stat(zf, "_rels/.rels", 0, &sb) == 0) {
        tmf->relationships_file = strdup("_rels/.rels");
    }

    // Check 3D model part
    if (zip_stat(zf, "3D/3dmodel.model", 0, &sb) == 0) {
        zip_file_t* f = zip_fopen(zf, "3D/3dmodel.model", 0);
        char* contents = (char*)malloc(sb.size + 1);
        zip_fread(f, contents, sb.size);
        contents[sb.size] = '\0';
        xmlDocPtr doc = xmlReadMemory(contents, sb.size, NULL, NULL, 0);
        xmlNodePtr root = xmlDocGetRootElement(doc);
        tmf->model_part = strdup("3D/3dmodel.model");
        tmf->custom_metadata = (char**)malloc(10 * sizeof(char*));
        int i = 0;
        for (xmlNodePtr node = root->children; node; node = node->next) {
            if (xmlStrcmp(node->name, (const xmlChar*)"metadata") == 0) {
                xmlChar* name = xmlGetProp(node, (const xmlChar*)"name");
                xmlChar* value = xmlNodeGetContent(node);
                char* meta = (char*)malloc(strlen((char*)name) + strlen((char*)value) + 4);
                sprintf(meta, "%s: %s", name, value);
                tmf->custom_metadata[i++] = meta;
                xmlFree(name);
                xmlFree(value);
            }
        }
        tmf->metadata_count = i;
        // Check extensions
        xmlNsPtr ns = root->nsDecls;
        tmf->extensions = (char**)malloc(10 * sizeof(char*));
        i = 0;
        while (ns) {
            if (strncmp((char*)ns->href, "http://schemas.3mf.io/", 21) == 0) {
                tmf->extensions[i++] = strdup((char*)ns->href);
            }
            ns = ns->next;
        }
        tmf->extensions_count = i;
        free(contents);
        xmlFreeDoc(doc);
        zip_fclose(f);
    }

    // Check optional parts
    zip_stat_init(&sb);
    for (zip_int64_t i = 0; i < zip_get_num_entries(zf, 0); i++) {
        if (zip_stat_index(zf, i, 0, &sb) == 0) {
            if (strstr(sb.name, ".jpg") || strstr(sb.name, ".png") && strstr(sb.name, "thumbnail")) {
                tmf->thumbnail = strdup(sb.name);
            }
            if (strstr(sb.name, "PrintTicket.xml")) {
                tmf->print_ticket = strdup(sb.name);
            }
            if (strstr(sb.name, "DigitalSignature")) {
                tmf->digital_signatures = strdup(sb.name);
            }
        }
    }

    zip_close(zf);
    return 0;
}

int ThreeMFFile_write(ThreeMFFile* tmf, const char* output_path) {
    int err;
    zip_t* zf = zip_open(output_path, ZIP_CREATE | ZIP_TRUNCATE, &err);
    if (!zf) {
        printf("Error creating .3MF file: %d\n", err);
        return -1;
    }

    // Write 3D model part
    xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
    xmlNodePtr model = xmlNewNode(NULL, (const xmlChar*)"model");
    xmlNsPtr ns = xmlNewNs(model, (const xmlChar*)"http://schemas.openxmlformats.org/3dmanufacturing/core/2015/02", NULL);
    xmlSetNs(model, ns);
    for (int i = 0; i < tmf->metadata_count; i++) {
        char* meta = tmf->custom_metadata[i];
        char* name = strtok(meta, ": ");
        char* value = strtok(NULL, "");
        xmlNodePtr meta_node = xmlNewNode(NULL, (const xmlChar*)"metadata");
        xmlSetProp(meta_node, (const xmlChar*)"name", (const xmlChar*)name);
        xmlNodeSetContent(meta_node, (const xmlChar*)value);
        xmlAddChild(model, meta_node);
    }
    xmlDocSetRootElement(doc, model);
    xmlChar* xmlbuff;
    int buffersize;
    xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
    zip_file_add(zf, "3D/3dmodel.model", zip_source_buffer(zf, xmlbuff, buffersize, 1), ZIP_FL_OVERWRITE);
    xmlFreeDoc(doc);

    // Write content types
    doc = xmlNewDoc((const xmlChar*)"1.0");
    xmlNodePtr types = xmlNewNode(NULL, (const xmlChar*)"Types");
    ns = xmlNewNs(types, (const xmlChar*)"http://schemas.openxmlformats.org/package/2006/content-types", NULL);
    xmlSetNs(types, ns);
    xmlNodePtr def = xmlNewNode(NULL, (const xmlChar*)"Default");
    xmlSetProp(def, (const xmlChar*)"Extension", (const xmlChar*)"model");
    xmlSetProp(def, (const xmlChar*)"ContentType", (const xmlChar*)"application/vnd.ms-package.3dmanufacturing-3dmodel+xml");
    xmlAddChild(types, def);
    xmlDocSetRootElement(doc, types);
    xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
    zip_file_add(zf, "[Content_Types].xml", zip_source_buffer(zf, xmlbuff, buffersize, 1), ZIP_FL_OVERWRITE);
    xmlFreeDoc(doc);

    // Write relationships
    doc = xmlNewDoc((const xmlChar*)"1.0");
    xmlNodePtr rels = xmlNewNode(NULL, (const xmlChar*)"Relationships");
    ns = xmlNewNs(rels, (const xmlChar*)"http://schemas.openxmlformats.org/package/2006/relationships", NULL);
    xmlSetNs(rels, ns);
    xmlNodePtr rel = xmlNewNode(NULL, (const xmlChar*)"Relationship");
    xmlSetProp(rel, (const xmlChar*)"Target", (const xmlChar*)"/3D/3dmodel.model");
    xmlSetProp(rel, (const xmlChar*)"Id", (const xmlChar*)"rel0");
    xmlSetProp(rel, (const xmlChar*)"Type", (const xmlChar*)"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel");
    xmlAddChild(rels, rel);
    xmlDocSetRootElement(doc, rels);
    xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
    zip_file_add(zf, "_rels/.rels", zip_source_buffer(zf, xmlbuff, buffersize, 1), ZIP_FL_OVERWRITE);
    xmlFreeDoc(doc);

    // Copy thumbnail if exists
    if (tmf->thumbnail) {
        zip_t* zf_in = zip_open(tmf->filepath, ZIP_RDONLY, &err);
        zip_file_t* f = zip_fopen(zf_in, tmf->thumbnail, 0);
        char* contents = (char*)malloc(sb.size);
        zip_fread(f, contents, sb.size);
        zip_file_add(zf, tmf->thumbnail, zip_source_buffer(zf, contents, sb.size, 1), ZIP_FL_OVERWRITE);
        zip_fclose(f);
        zip_close(zf_in);
    }

    zip_close(zf);
    return 0;
}

void ThreeMFFile_print(ThreeMFFile* tmf) {
    printf("file_extension: %s\n", tmf->file_extension);
    printf("mime_type: ");
    for (int i = 0; i < tmf->mime_type_count; i++) printf("%s ", tmf->mime_type[i]);
    printf("\n");
    printf("container_format: %s\n", tmf->container_format);
    printf("model_part: %s\n", tmf->model_part ? tmf->model_part : "null");
    printf("relationships_file: %s\n", tmf->relationships_file ? tmf->relationships_file : "null");
    printf("content_types_file: %s\n", tmf->content_types_file ? tmf->content_types_file : "null");
    printf("thumbnail: %s\n", tmf->thumbnail ? tmf->thumbnail : "null");
    printf("print_ticket: %s\n", tmf->print_ticket ? tmf->print_ticket : "null");
    printf("digital_signatures: %s\n", tmf->digital_signatures ? tmf->digital_signatures : "null");
    printf("custom_metadata: ");
    for (int i = 0; i < tmf->metadata_count; i++) printf("%s ", tmf->custom_metadata[i]);
    printf("\n");
    printf("extensions: ");
    for (int i = 0; i < tmf->extensions_count; i++) printf("%s ", tmf->extensions[i]);
    printf("\n");
}

int main() {
    ThreeMFFile* tmf = ThreeMFFile_new("example.3mf");
    ThreeMFFile_read(tmf);
    ThreeMFFile_print(tmf);
    ThreeMFFile_write(tmf, "output.3mf");
    ThreeMFFile_free(tmf);
    return 0;
}

This requires libzip and libxml2 (install via apt-get install libzip-dev libxml2-dev on Ubuntu, for example). Compile with gcc -o 3mf 3mf.c -lzip -lxml2 -I/usr/include/libxml2. It handles the .3MF structure similarly to the other implementations.


Notes and Limitations

  • Dependencies: The JavaScript class requires jszip and xmldom. The C implementation requires libzip and libxml2. Ensure these are installed.
  • Minimal Writing: The write methods create minimal .3MF files with basic structure (model, content types, relationships) to demonstrate functionality. They preserve metadata and thumbnails but do not fully replicate complex model data or extensions.
  • Error Handling: Each implementation includes basic error handling but assumes valid .3MF files. Add validation for production use.
  • Testing: Test with a valid .3MF file (e.g., from the 3MF core examples repository on GitHub). Create a sample .3MF file using software like Microsoft 3D Builder or Ultimaker Cura.
  • Extensions: The code detects extensions via XML namespaces but does not process their specific data (e.g., beam lattice or slice data) to keep the implementation focused on intrinsic file system properties.
  • Portability: The JavaScript code is Node.js-specific. For browser use, adapt it to use browser-native APIs like FileReader and JSZip.

For detailed specifications, refer to the 3MF Core Specification v1.3.0 and extensions at 3mf.io or the GitHub repository 3MFConsortium/spec_core. If you need further clarification or enhancements (e.g., specific extension handling), let me know

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

The .3MF file format is based on the Open Packaging Conventions (OPC) and uses a ZIP container. Intrinsic properties refer to the structural and metadata elements defined in the format specification that are stored within the package, such as package-level metadata in the core 3D model XML file. Based on the official 3MF Core Specification, the key properties that can be read and written are the standard metadata elements in the <model> XML root. These are:

  • Title: A string representing the title of the 3D model.
  • Designer: A string indicating the designer or author of the model.
  • Description: A string providing a description of the model.
  • Copyright: A string containing copyright information.
  • LicenseTerms: A string specifying license terms.
  • Rating: A string representing a rating for the model.
  • CreationDate: A string in date format (e.g., YYYY-MM-DD) indicating when the model was created.
  • ModificationDate: A string in date format (e.g., YYYY-MM-DD) indicating when the model was last modified.

These properties are stored as <metadata> child elements under the <model> root in the required /3D/3dmodel.model XML file inside the ZIP archive. Custom metadata can exist but is not considered intrinsic. Other format elements like thumbnails, textures, and print tickets are optional and not listed as core properties here, as they are not always present.

2. Python Class

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

class ThreeMFHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}
        self.namespaces = {'': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02'}  # Default namespace from spec

    def _get_model_xml(self, zf):
        if '3D/3dmodel.model' in zf.namelist():
            with zf.open('3D/3dmodel.model') as f:
                return ET.parse(f)
        raise ValueError("Invalid .3MF file: Missing 3D/3dmodel.model")

    def read_properties(self):
        with zipfile.ZipFile(self.filename, 'r') as zf:
            tree = self._get_model_xml(zf)
            root = tree.getroot()
            for meta in root.findall('metadata', self.namespaces):
                name = meta.get('name')
                value = meta.text
                if name in ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate']:
                    self.properties[name] = value
        return self.properties

    def write_properties(self, new_properties):
        with zipfile.ZipFile(self.filename, 'r') as zf_in:
            with zipfile.ZipFile(self.filename + '.tmp', 'w') as zf_out:
                tree = self._get_model_xml(zf_in)
                root = tree.getroot()
                # Remove existing metadata
                for meta in root.findall('metadata', self.namespaces):
                    if meta.get('name') in new_properties:
                        root.remove(meta)
                # Add new metadata
                for name, value in new_properties.items():
                    if name in ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate']:
                        meta_elem = ET.SubElement(root, 'metadata')
                        meta_elem.set('name', name)
                        meta_elem.text = value
                # Write updated model XML
                model_xml = BytesIO()
                tree.write(model_xml, encoding='utf-8', xml_declaration=True)
                model_info = zipfile.ZipInfo('3D/3dmodel.model')
                zf_out.writestr(model_info, model_xml.getvalue())
                # Copy other files
                for item in zf_in.infolist():
                    if item.filename != '3D/3dmodel.model':
                        zf_out.writestr(item, zf_in.read(item.filename))
        # Replace original file
        import os
        os.replace(self.filename + '.tmp', self.filename)

3. Java Class

import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class ThreeMFHandler {
    private String filename;
    private Map<String, String> properties = new HashMap<>();
    private static final String MODEL_PATH = "3D/3dmodel.model";
    private static final String NAMESPACE = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02";

    public ThreeMFHandler(String filename) {
        this.filename = filename;
    }

    private Document getModelDocument(ZipFile zf) throws IOException, ParserConfigurationException, SAXException {
        ZipEntry entry = zf.getEntry(MODEL_PATH);
        if (entry == null) {
            throw new IOException("Invalid .3MF file: Missing " + MODEL_PATH);
        }
        InputStream is = zf.getInputStream(entry);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        return dbf.newDocumentBuilder().parse(is);
    }

    public Map<String, String> readProperties() throws IOException, ParserConfigurationException, SAXException {
        try (ZipFile zf = new ZipFile(filename)) {
            Document doc = getModelDocument(zf);
            NodeList metas = doc.getElementsByTagNameNS(NAMESPACE, "metadata");
            for (int i = 0; i < metas.getLength(); i++) {
                Element meta = (Element) metas.item(i);
                String name = meta.getAttribute("name");
                String value = meta.getTextContent();
                if (isStandardProperty(name)) {
                    properties.put(name, value);
                }
            }
        }
        return properties;
    }

    public void writeProperties(Map<String, String> newProperties) throws Exception {
        String tempFile = filename + ".tmp";
        try (ZipFile zfIn = new ZipFile(filename);
             ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tempFile))) {
            Document doc = getModelDocument(zfIn);
            Element root = doc.getDocumentElement();
            // Remove existing
            NodeList metas = doc.getElementsByTagNameNS(NAMESPACE, "metadata");
            for (int i = metas.getLength() - 1; i >= 0; i--) {
                Element meta = (Element) metas.item(i);
                if (newProperties.containsKey(meta.getAttribute("name"))) {
                    root.removeChild(meta);
                }
            }
            // Add new
            for (Map.Entry<String, String> entry : newProperties.entrySet()) {
                String name = entry.getKey();
                if (isStandardProperty(name)) {
                    Element meta = doc.createElementNS(NAMESPACE, "metadata");
                    meta.setAttribute("name", name);
                    meta.setTextContent(entry.getValue());
                    root.appendChild(meta);
                }
            }
            // Write updated XML
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer t = tf.newTransformer();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            t.transform(new DOMSource(doc), new StreamResult(baos));
            ZipEntry ze = new ZipEntry(MODEL_PATH);
            zos.putNextEntry(ze);
            zos.write(baos.toByteArray());
            zos.closeEntry();
            // Copy other entries
            Enumeration<? extends ZipEntry> entries = zfIn.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (!entry.getName().equals(MODEL_PATH)) {
                    zos.putNextEntry(entry);
                    InputStream is = zfIn.getInputStream(entry);
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = is.read(buf)) > 0) {
                        zos.write(buf, 0, len);
                    }
                    zos.closeEntry();
                }
            }
        }
        new File(filename).delete();
        new File(tempFile).renameTo(new File(filename));
    }

    private boolean isStandardProperty(String name) {
        return Set.of("Title", "Designer", "Description", "Copyright", "LicenseTerms", "Rating", "CreationDate", "ModificationDate").contains(name);
    }
}

4. JavaScript Class

const fs = require('fs');
const JSZip = require('jszip');
const { DOMParser, XMLSerializer } = require('xmldom');

class ThreeMFHandler {
    constructor(filename) {
        this.filename = filename;
        this.properties = {};
        this.namespace = 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02';
    }

    async readProperties() {
        const data = fs.readFileSync(this.filename);
        const zip = await JSZip.loadAsync(data);
        const modelFile = zip.file('3D/3dmodel.model');
        if (!modelFile) {
            throw new Error('Invalid .3MF file: Missing 3D/3dmodel.model');
        }
        const xmlStr = await modelFile.async('string');
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlStr, 'text/xml');
        const metas = doc.getElementsByTagNameNS(this.namespace, 'metadata');
        for (let i = 0; i < metas.length; i++) {
            const meta = metas[i];
            const name = meta.getAttribute('name');
            const value = meta.textContent;
            if (this.isStandardProperty(name)) {
                this.properties[name] = value;
            }
        }
        return this.properties;
    }

    async writeProperties(newProperties) {
        const data = fs.readFileSync(this.filename);
        const zip = await JSZip.loadAsync(data);
        const modelFile = zip.file('3D/3dmodel.model');
        if (!modelFile) {
            throw new Error('Invalid .3MF file: Missing 3D/3dmodel.model');
        }
        const xmlStr = await modelFile.async('string');
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlStr, 'text/xml');
        const root = doc.documentElement;
        // Remove existing
        const metas = doc.getElementsByTagNameNS(this.namespace, 'metadata');
        for (let i = metas.length - 1; i >= 0; i--) {
            const meta = metas[i];
            if (newProperties[meta.getAttribute('name')]) {
                root.removeChild(meta);
            }
        }
        // Add new
        for (const [name, value] of Object.entries(newProperties)) {
            if (this.isStandardProperty(name)) {
                const meta = doc.createElementNS(this.namespace, 'metadata');
                meta.setAttribute('name', name);
                meta.textContent = value;
                root.appendChild(meta);
            }
        }
        const serializer = new XMLSerializer();
        const updatedXml = serializer.serializeToString(doc);
        zip.file('3D/3dmodel.model', updatedXml);
        const updatedData = await zip.generateAsync({ type: 'nodebuffer' });
        fs.writeFileSync(this.filename, updatedData);
    }

    isStandardProperty(name) {
        const standards = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate'];
        return standards.includes(name);
    }
}

5. C Class (C++ Implementation)

#include <iostream>
#include <string>
#include <map>
#include <zip.h>
#include <tinyxml2.h>  // Assuming tinyxml2 library for XML parsing

class ThreeMFHandler {
private:
    std::string filename;
    std::map<std::string, std::string> properties;
    const std::string modelPath = "3D/3dmodel.model";
    const std::string ns = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02";

public:
    ThreeMFHandler(const std::string& fn) : filename(fn) {}

    std::map<std::string, std::string> readProperties() {
        zip_t* za = zip_open(filename.c_str(), ZIP_RDONLY, nullptr);
        if (!za) {
            throw std::runtime_error("Failed to open .3MF file");
        }
        zip_file_t* zf = zip_fopen(za, modelPath.c_str(), 0);
        if (!zf) {
            zip_close(za);
            throw std::runtime_error("Invalid .3MF file: Missing " + modelPath);
        }
        zip_stat_t sb;
        zip_stat(za, modelPath.c_str(), 0, &sb);
        char* buffer = new char[sb.size + 1];
        zip_fread(zf, buffer, sb.size);
        buffer[sb.size] = '\0';
        zip_fclose(zf);
        zip_close(za);

        tinyxml2::XMLDocument doc;
        doc.Parse(buffer);
        delete[] buffer;
        tinyxml2::XMLElement* root = doc.FirstChildElement("model");
        if (!root) {
            throw std::runtime_error("Invalid XML in model file");
        }
        for (tinyxml2::XMLElement* meta = root->FirstChildElement("metadata"); meta; meta = meta->NextSiblingElement("metadata")) {
            const char* name = meta->Attribute("name");
            const char* value = meta->GetText();
            if (name && value && isStandardProperty(name)) {
                properties[name] = value;
            }
        }
        return properties;
    }

    void writeProperties(const std::map<std::string, std::string>& newProperties) {
        // Read original ZIP and extract model XML
        zip_t* zaIn = zip_open(filename.c_str(), ZIP_RDONLY, nullptr);
        if (!zaIn) {
            throw std::runtime_error("Failed to open .3MF file");
        }
        zip_file_t* zf = zip_fopen(zaIn, modelPath.c_str(), 0);
        if (!zf) {
            zip_close(zaIn);
            throw std::runtime_error("Invalid .3MF file: Missing " + modelPath);
        }
        zip_stat_t sb;
        zip_stat(zaIn, modelPath.c_str(), 0, &sb);
        char* buffer = new char[sb.size + 1];
        zip_fread(zf, buffer, sb.size);
        buffer[sb.size] = '\0';
        zip_fclose(zf);

        tinyxml2::XMLDocument doc;
        doc.Parse(buffer);
        delete[] buffer;
        tinyxml2::XMLElement* root = doc.FirstChildElement("model");
        if (!root) {
            zip_close(zaIn);
            throw std::runtime_error("Invalid XML in model file");
        }
        // Remove existing
        tinyxml2::XMLElement* meta = root->FirstChildElement("metadata");
        while (meta) {
            tinyxml2::XMLElement* next = meta->NextSiblingElement("metadata");
            const char* name = meta->Attribute("name");
            if (name && newProperties.find(name) != newProperties.end()) {
                root->DeleteChild(meta);
            }
            meta = next;
        }
        // Add new
        for (const auto& pair : newProperties) {
            if (isStandardProperty(pair.first)) {
                tinyxml2::XMLElement* newMeta = doc.NewElement("metadata");
                newMeta->SetAttribute("name", pair.first.c_str());
                newMeta->SetText(pair.second.c_str());
                root->InsertEndChild(newMeta);
            }
        }
        // Save updated XML to string
        tinyxml2::XMLPrinter printer;
        doc.Print(&printer);
        std::string updatedXml = printer.CStr();

        // Write to temp ZIP
        std::string tempFile = filename + ".tmp";
        zip_t* zaOut = zip_open(tempFile.c_str(), ZIP_CREATE | ZIP_TRUNCATE, nullptr);
        if (!zaOut) {
            zip_close(zaIn);
            throw std::runtime_error("Failed to create temp .3MF file");
        }
        zip_source_t* src = zip_source_buffer(zaOut, updatedXml.data(), updatedXml.size(), 0);
        if (zip_file_add(zaOut, modelPath.c_str(), src, ZIP_FL_OVERWRITE) < 0) {
            zip_source_free(src);
            zip_close(zaOut);
            zip_close(zaIn);
            throw std::runtime_error("Failed to add updated model XML");
        }
        // Copy other files
        int numEntries = zip_get_num_entries(zaIn, 0);
        for (int i = 0; i < numEntries; i++) {
            const char* name = zip_get_name(zaIn, i, 0);
            if (std::string(name) != modelPath) {
                zip_file_t* zfCopy = zip_fopen_index(zaIn, i, 0);
                zip_stat_index(zaIn, i, 0, &sb);
                char* copyBuffer = new char[sb.size];
                zip_fread(zfCopy, copyBuffer, sb.size);
                zip_fclose(zfCopy);
                zip_source_t* copySrc = zip_source_buffer(zaOut, copyBuffer, sb.size, 1);  // 1 to free buffer
                if (zip_file_add(zaOut, name, copySrc, ZIP_FL_OVERWRITE) < 0) {
                    zip_source_free(copySrc);
                    zip_close(zaOut);
                    zip_close(zaIn);
                    throw std::runtime_error("Failed to copy file: " + std::string(name));
                }
            }
        }
        zip_close(zaOut);
        zip_close(zaIn);
        std::remove(filename.c_str());
        std::rename(tempFile.c_str(), filename.c_str());
    }

private:
    bool isStandardProperty(const std::string& name) {
        static const std::set<std::string> standards = {
            "Title", "Designer", "Description", "Copyright", "LicenseTerms", "Rating", "CreationDate", "ModificationDate"
        };
        return standards.count(name);
    }
};