@@ -2,14 +2,29 @@ import Foundation
22import Zip
33import Nodal
44
5+ /// Writes 3MF packages to either a file URL or in‑memory data, managing models, related files, and relationships.
6+ ///
7+ /// PackageWriter abstracts the underlying ZIP archive and handles:
8+ /// - Writing the root model and any additional models
9+ /// - Adding textures, thumbnails, and other related files with appropriate content types and relationships
10+ /// - Finalizing to disk (URL) or returning in‑memory Data
11+ ///
12+ /// Usage:
13+ /// - Initialize with a URL (for on‑disk output) or with no parameters (for in‑memory output)
14+ /// - Populate `model` and add any additional files or models
15+ /// - Call `finalize()` to write the package
16+ ///
517public class PackageWriter < Target> {
618 private let archive : ZipArchive < Target >
719 private var contentTypes = ContentTypes ( )
820 private var relationships = Relationships ( )
921 private var modelFileRelationships = Relationships ( )
1022 private var additionalModels : [ String : Model ] = [ : ]
1123
24+ /// The root model written as the package's primary model.
1225 public var model = Model ( )
26+
27+ /// The compression level used for files added to the package.
1328 public var compressionLevel = CompressionLevel . default
1429
1530 private init ( archive: ZipArchive < Target > ) {
@@ -19,30 +34,45 @@ public class PackageWriter<Target> {
1934}
2035
2136public extension PackageWriter < URL > {
37+ /// Creates a writer that outputs a 3MF package to a file URL.
38+ ///
39+ /// - Parameter fileURL: The destination file URL where the 3MF package will be written.
40+ /// - Throws: An error if the archive cannot be created or opened for writing.
2241 convenience init ( url fileURL: URL ) throws {
2342 try self . init ( archive: ZipArchive ( url: fileURL, mode: . overwrite) )
2443 }
2544
26- // Write the 3MF file to disk
45+ /// Finalizes and writes the 3MF package to disk.
46+ ///
47+ /// After calling this method, the writer can no longer be used to add files.
48+ /// - Throws: An error if writing or finalizing the archive fails.
2749 func finalize( ) throws {
2850 try writeMainFiles ( )
2951 try archive. finalize ( )
3052 }
3153}
3254
3355public extension PackageWriter < Data > {
56+ /// Creates a writer that builds a 3MF package in memory.
3457 convenience init ( ) {
3558 self . init ( archive: ZipArchive ( ) )
3659 }
3760
38- // Generate the 3MF data
61+ /// Finalizes and returns the 3MF package as data.
62+ ///
63+ /// - Returns: The generated 3MF archive data.
64+ /// - Throws: An error if writing or finalizing the archive fails.
3965 func finalize( ) throws -> Data {
4066 try writeMainFiles ( )
4167 try writeMetaFiles ( )
4268 return try archive. finalize ( )
4369 }
4470
45- // Generate the 3MF data
71+ /// Finalizes and returns the 3MF package as data.
72+ ///
73+ /// This async variant allows preparing files concurrently when needed.
74+ /// - Returns: The generated 3MF archive data.
75+ /// - Throws: An error if writing or finalizing the archive fails.
4676 func finalize( ) async throws -> Data {
4777 try await writeMainFiles ( )
4878 try writeMetaFiles ( )
@@ -51,6 +81,15 @@ public extension PackageWriter<Data> {
5181}
5282
5383public extension PackageWriter {
84+ /// Adds an arbitrary file to the package at the specified URL, with optional content type and relationship metadata.
85+ ///
86+ /// - Parameters:
87+ /// - url: The destination URL within the package.
88+ /// - mimeType: The file's MIME type to be recorded in content types. Pass `nil` to skip.
89+ /// - relationshipType: An optional relationship type to record. Pass `nil` to skip.
90+ /// - relativeToRootModel: Whether to attach the relationship to the root model file instead of the package root.
91+ /// - data: The file contents.
92+ /// - Throws: An error if the file cannot be added to the archive.
5493 func addFile(
5594 at url: URL ,
5695 contentType mimeType: String ? ,
@@ -71,6 +110,12 @@ public extension PackageWriter {
71110 try addFile ( at: url, data: data)
72111 }
73112
113+ /// Adds a texture to the package and returns its assigned URL.
114+ ///
115+ /// The file is numbered automatically and registered with the appropriate content type and relationship.
116+ /// - Parameter data: The texture file data.
117+ /// - Returns: The URL assigned to the texture within the package.
118+ /// - Throws: An error if the file cannot be added.
74119 func addTexture( data: Data ) throws -> URL {
75120 try addNumberedFile (
76121 base: " Textures/Texture " ,
@@ -80,6 +125,14 @@ public extension PackageWriter {
80125 )
81126 }
82127
128+ /// Adds a thumbnail to the package and returns its assigned URL.
129+ ///
130+ /// The file is numbered automatically and registered with the appropriate content type and relationship.
131+ /// - Parameters:
132+ /// - data: The thumbnail image data.
133+ /// - mimeType: The thumbnail image MIME type (e.g., "image/png").
134+ /// - Returns: The URL assigned to the thumbnail within the package.
135+ /// - Throws: An error if the file cannot be added.
83136 func addThumbnail( data: Data , mimeType: String ) throws -> URL {
84137 try addNumberedFile (
85138 base: " Metadata/Thumbnail " ,
@@ -89,6 +142,14 @@ public extension PackageWriter {
89142 )
90143 }
91144
145+ /// Registers an additional model to be included in the package and returns the URL it will be written to.
146+ ///
147+ /// If a model with the same name already exists, a numeric suffix is added to ensure uniqueness.
148+ /// - Parameters:
149+ /// - model: The additional model to include.
150+ /// - name: The desired base name (without extension) of the model file.
151+ /// - Returns: The URL where the model will be written inside the package.
152+ /// - Throws: An error if the name is invalid.
92153 func addAdditionalModel( _ model: Model , named name: String ) throws -> URL {
93154 var name = name
94155 var counter = 2
0 commit comments