Skip to content

Commit 0b7a941

Browse files
committed
Add docs for PackageReader and PackageWriter
1 parent a1659d8 commit 0b7a941

2 files changed

Lines changed: 96 additions & 3 deletions

File tree

Sources/ThreeMF/Package/PackageReader.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ import Foundation
22
import Zip
33
import Nodal
44

5+
/// Reads 3MF packages from either a file URL or in‑memory data and provides access to parsed models and file contents.
6+
///
7+
/// PackageReader abstracts the underlying ZIP archive and exposes high‑level methods to:
8+
/// - Load the root model or a model at a specific path inside the package
9+
/// - Read raw file data from a given URL within the package
10+
///
11+
/// Usage:
12+
/// - Initialize with a URL or Data
13+
/// - Call `model()` to load the root model, or `model(at:)` to load a specific model file
14+
/// - Use `readFile(at:)` to access arbitrary file contents inside the package
15+
///
516
public struct PackageReader<Target> {
617
private let archive: ZipArchive<Target>
718

@@ -11,16 +22,27 @@ public struct PackageReader<Target> {
1122
}
1223

1324
public extension PackageReader<URL> {
25+
/// Creates a reader that opens a 3MF package from a file URL.
26+
///
27+
/// - Parameter fileURL: The URL of the 3MF package to open.
28+
/// - Throws: An error if the archive cannot be opened.
1429
init(url fileURL: URL) throws {
1530
try self.init(archive: ZipArchive(url: fileURL, mode: .readOnly))
1631
}
1732

33+
/// Closes the underlying archive and releases associated resources.
34+
///
35+
/// Call this when you are done reading from the package to ensure resources are freed promptly.
1836
func invalidate() {
1937
archive.close()
2038
}
2139
}
2240

2341
public extension PackageReader<Data> {
42+
/// Creates a reader that opens a 3MF package from in‑memory data.
43+
///
44+
/// - Parameter data: The 3MF package data.
45+
/// - Throws: An error if the archive cannot be opened.
2446
init(data: Data) throws {
2547
try self.init(archive: ZipArchive(data: data))
2648
}
@@ -58,10 +80,20 @@ internal extension PackageReader {
5880
}
5981

6082
public extension PackageReader {
83+
/// Loads and parses a 3MF model from the package.
84+
///
85+
/// - Parameter path: The URL of a specific model within the package. If `nil`, the package's root model is loaded.
86+
/// - Returns: A parsed `Model` instance.
87+
/// - Throws: Errors related to reading or parsing the model from the package.
6188
func model(at path: URL? = nil) throws -> Model {
6289
return try Model(from: modelRootElement(at: path))
6390
}
6491

92+
/// Reads raw data for a file stored within the package.
93+
///
94+
/// - Parameter url: The URL of the file within the package (e.g., a texture or auxiliary asset).
95+
/// - Returns: The file data if present, or `nil` if the file cannot be found.
96+
/// - Throws: An error if the archive cannot be read.
6597
func readFile(at url: URL) throws -> Data? {
6698
var filePath = url.path
6799
if filePath.hasPrefix("/") {

Sources/ThreeMF/Package/PackageWriter.swift

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@ import Foundation
22
import Zip
33
import 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+
///
517
public 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

2136
public 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

3355
public 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

5383
public 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

Comments
 (0)