11import Foundation
22
3+ /// A model that will be rendered into a standard file format.
4+ ///
5+ /// Use `ModelFileGenerator` to build geometry into standard file formats like 3MF, STL, or SVG.
6+ /// The model is rendered into a `ModelFile`, from which data can be accessed in memory or written
7+ /// to disk.
8+ ///
9+ /// ```swift
10+ /// let modelFile = try await ModelFileGenerator.build(named: "my-part") {
11+ /// Box(x: 10, y: 10, z: 5)
12+ /// }
13+ ///
14+ /// let fileName = modelFile.suggestedFileName
15+ /// let fileData = try await modelFile.data()
16+ /// ```
17+ ///
18+ /// For command-line apps, see ``Model`` for a pre-built convenient workflow for outputting to
19+ /// the current working directory.
320public struct ModelFileGenerator {
4- private let evaluationContext = EvaluationContext ( )
21+
22+ /// Render a one-shot model to a model file.
23+ ///
24+ /// For more details, see the documentation for the ``ModelFileGenerator.build()``
25+ /// instance method.
26+ ///
27+ /// - Note: If you intend to render geometries more than once, create an instance of
28+ /// ``ModelFileGenerator`` and re-use ``build()`` on that instance instead.
29+ /// Instances maintain a cache for improved performance over multiple builds.
30+ public static func build(
31+ named name: String ? = nil ,
32+ options: ModelOptions ... ,
33+ @ModelContentBuilder content: @Sendable @escaping ( ) -> [ BuildDirective ]
34+ ) async throws -> ModelFile {
35+ return try await ModelFileGenerator ( ) . build ( named: name, options: options, content: content)
36+ }
537
38+ private let evaluationContext = EvaluationContext ( )
39+
40+ /// Creates a ``ModelFileGenerator`` instance. Instances maintain a cache, allowing improved
41+ /// performance when performing multiple, subsequent builds.
642 public init ( ) { }
743
44+ /// Renders a model to a standard file format based on the provided geometry.
45+ ///
46+ /// Use this function to construct a 3D or 2D model to a file which can then be used in memory
47+ /// or written to disk. The model is generated from a geometry tree you define using the result
48+ /// builder. Supported output formats include 3MF, STL, and SVG, and can be customized via `ModelOptions`.
49+ ///
50+ /// The model will be rendered into a ``ModelFile`` object which can be used to access the file's
51+ /// contents, get a suggested file name, and write the file to disk.
52+ ///
53+ /// In addition to geometry, the model’s result builder also accepts:
54+ /// - `Metadata(...)`: Attaches metadata (e.g. title, author, license) that is merged into the model’s options.
55+ /// - `Environment { … }` or `Environment(\.keyPath, value)`: Applies environment customizations for this model.
56+ ///
57+ /// Precedence and merging rules:
58+ /// - `Environment` directives inside the model’s builder form the base.
59+ /// - `Metadata` inside the model’s builder is merged into the model’s options.
60+ ///
61+ /// - Parameters:
62+ /// - name: The base name of the model, which will be used to generate a suggested file name.
63+ /// - options: One or more `ModelOptions` used to customize output format, compression, metadata, etc.
64+ /// - content: A result builder that builds the model geometry, and may also include `Environment` and `Metadata`.
65+ ///
66+ /// - Returns: Returns the constructed file in the form of a ``ModelFile`` object.
67+ ///
68+ /// ### Examples
69+ ///
70+ /// ```swift
71+ /// let fileData: Data = try await ModelFileGenerator.build(named: "simple") {
72+ /// Box(x: 10, y: 10, z: 5)
73+ /// }.data()
74+ /// ```
75+ ///
76+ /// ```swift
77+ /// let modelGenerator = ModelFileGenerator()
78+ /// let file: ModelFile = try await modelGenerator.build(named: "complex", options: .format3D(.threeMF)) {
79+ /// // Model-local metadata and environment
80+ /// Metadata(title: "Complex", description: "A more complex example of using ModelFileGenerator")
81+ ///
82+ /// Environment {
83+ /// $0.segmentation = .adaptive(minAngle: 10°, minSize: 0.5)
84+ /// }
85+ ///
86+ /// Box(x: 100, y: 3, z: 20)
87+ /// .deformed(by: BezierPath2D {
88+ /// curve(controlX: 50, controlY: 50, endX: 100, endY: 0)
89+ /// })
90+ /// }
91+ ///
92+ /// let url = try await presentSaveDialog(defaultName: file.suggestedFileName)
93+ /// try await file.write(to: url)
94+ /// ```
895 public func build(
996 named name: String ? = nil ,
1097 options: ModelOptions ... ,
1198 @ModelContentBuilder content: @Sendable @escaping ( ) -> [ BuildDirective ]
1299 ) async throws -> ModelFile {
100+ return try await build ( named: name, options: options, content: content)
101+ }
102+
103+ internal func build(
104+ named name: String ? = nil ,
105+ options: [ ModelOptions ] ,
106+ @ModelContentBuilder content: @Sendable @escaping ( ) -> [ BuildDirective ]
107+ ) async throws -> ModelFile {
108+ // This is here because in Swift, a varadic parameter can't be passed along as a varadic
109+ // parameter (i.e., the static method can't call the instance method without this).
13110 let directives = content ( )
14111 let options = ModelOptions ( options) . adding ( modelName: name, directives: directives)
15112 let environment = EnvironmentValues . defaultEnvironment. adding ( directives: directives, modelOptions: options)
@@ -20,6 +117,7 @@ public struct ModelFileGenerator {
20117 }
21118}
22119
120+ /// A representation of a model in the form of a standard file format (3MF, STL, SVG, etc).
23121public struct ModelFile {
24122 private let dataProvider : OutputDataProvider
25123 private let evaluationContext : EvaluationContext
@@ -32,11 +130,15 @@ public struct ModelFile {
32130 self . modelName = modelName
33131 self . buildWarnings = buildWarnings
34132 }
35-
133+
134+ /// Any warnings generated during the build process.
36135 public let buildWarnings : [ BuildWarning ]
37-
136+
137+ /// The file's file extension, such as `3mf`, `stl`, etc.
38138 public var fileExtension : String { dataProvider. fileExtension }
39-
139+
140+ /// The file's suggested name, including extension, based on the model name given when built.
141+ /// Illegal file name characters will be removed based on the current platform.
40142 public var suggestedFileName : String {
41143 let invalidCharacters : String
42144 #if os(Windows)
@@ -58,11 +160,13 @@ public struct ModelFile {
58160 if sanitizedFileName. isEmpty { sanitizedFileName = " Model " }
59161 return " \( sanitizedFileName) . \( fileExtension) "
60162 }
61-
163+
164+ /// Generates the file's contents as in-memory data.
62165 public func data( ) async throws -> Data {
63166 try await dataProvider. generateOutput ( context: evaluationContext)
64167 }
65-
168+
169+ /// Writes the file's contents to the given location on disk.
66170 public func write( to fileURL: URL ) async throws {
67171 try await dataProvider. writeOutput ( to: fileURL, context: evaluationContext)
68172 }
0 commit comments