Skip to content

Commit cb8e88c

Browse files
committed
Simplify Model by extracting a lot of the logic
1 parent a124199 commit cb8e88c

File tree

6 files changed

+127
-75
lines changed

6 files changed

+127
-75
lines changed

Sources/Cadova/Abstract Layer/Geometry/References/ReferenceState.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,3 @@ extension ReferenceState {
7575
otherState.usedAnchors.intersection(definedAnchors.keys).count > 0 || otherState.usedTags.intersection(definedTags.keys).count > 0
7676
}
7777
}
78-
79-
extension ReferenceState {
80-
func printWarningsAtTopLevel() {
81-
if !undefinedAnchors.isEmpty {
82-
logger.warning("Undefined anchors: \(undefinedAnchors)")
83-
}
84-
if !undefinedTags.isEmpty {
85-
logger.warning("Undefined tags: \(undefinedTags)")
86-
}
87-
}
88-
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Foundation
2+
3+
extension [BuildDirective] {
4+
func build(
5+
with options: ModelOptions,
6+
in environment: EnvironmentValues,
7+
context: EvaluationContext
8+
) async throws -> (OutputDataProvider, [BuildWarning]) {
9+
let geometries3D = compactMap(\.geometry3D)
10+
let geometries2D = compactMap(\.geometry2D)
11+
12+
if geometries3D.count > 0 {
13+
let promotedFrom2D = geometries2D.map { $0.promotedTo3D() }
14+
let result = try await context.buildModelResult(for: Union(geometries3D + promotedFrom2D), in: environment)
15+
return (options.dataProvider(for: result), result.buildWarnings)
16+
17+
} else if geometries2D.count > 0 {
18+
let result = try await context.buildModelResult(for: Union(geometries2D), in: environment)
19+
return (options.dataProvider(for: result), result.buildWarnings)
20+
21+
} else {
22+
throw BuildError.noGeometry
23+
}
24+
}
25+
}
26+
27+
enum BuildError: Error {
28+
case noGeometry
29+
}
30+
31+
fileprivate extension Geometry2D {
32+
func promotedTo3D() -> any Geometry3D {
33+
extruded(height: 0.001)
34+
}
35+
}
36+
37+
internal extension EnvironmentValues {
38+
func adding(directives: [BuildDirective], modelOptions: ModelOptions) -> Self {
39+
var mutatingEnvironment = self
40+
for builder in directives.compactMap(\.environment) {
41+
builder(&mutatingEnvironment)
42+
}
43+
mutatingEnvironment.modelOptions = modelOptions
44+
return mutatingEnvironment
45+
}
46+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
3+
public enum BuildWarning {
4+
case onlyModifier
5+
case undefinedAnchors (Set<Anchor>)
6+
case undefinedTags (Set<Tag>)
7+
8+
public var description: String {
9+
switch self {
10+
case .onlyModifier:
11+
return "Model uses only() modifier; saving a partial geometry tree"
12+
13+
case .undefinedAnchors (let anchors):
14+
return "Undefined anchors: \(anchors)"
15+
16+
case .undefinedTags (let tags):
17+
return "Undefined tags: \(tags)"
18+
}
19+
}
20+
}
21+
22+
extension BuildResult {
23+
var buildWarnings: [BuildWarning] {
24+
var warnings: [BuildWarning] = []
25+
let referenceState = elements[ReferenceState.self]
26+
27+
if !referenceState.undefinedTags.isEmpty {
28+
warnings.append(.undefinedTags(referenceState.undefinedTags))
29+
}
30+
if !referenceState.undefinedAnchors.isEmpty {
31+
warnings.append(.undefinedAnchors(referenceState.undefinedAnchors))
32+
}
33+
if hasOnly {
34+
warnings.append(.onlyModifier)
35+
}
36+
return warnings
37+
}
38+
}

Sources/Cadova/Concrete Layer/Build/Model/Model.swift

Lines changed: 16 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,8 @@ public struct Model: Sendable {
9595
let directives = inheritedEnvironment.whileCurrent {
9696
self.directives()
9797
}
98-
let localOptions: ModelOptions = [
99-
.init(ModelName(name: name)),
100-
inheritedOptions ?? [],
101-
options,
102-
.init(directives.compactMap(\.options))
103-
]
104-
105-
var mutatingEnvironment = inheritedEnvironment
106-
for builder in directives.compactMap(\.environment) {
107-
builder(&mutatingEnvironment)
108-
}
109-
mutatingEnvironment.modelOptions = localOptions
110-
let environment = mutatingEnvironment
98+
let options = self.options.adding(modelName: name, defaults: inheritedOptions, directives: directives)
99+
let environment = inheritedEnvironment.adding(directives: directives, modelOptions: options)
111100

112101
let baseURL: URL
113102
if let parent = directory {
@@ -116,31 +105,24 @@ public struct Model: Sendable {
116105
baseURL = URL(expandingFilePath: name)
117106
}
118107

119-
let geometries3D = directives.compactMap(\.geometry3D)
120-
let geometries2D = directives.compactMap(\.geometry2D)
121108
let provider: OutputDataProvider
122109

123110
do {
124-
if geometries3D.count > 0 {
125-
let promotedFrom2D = geometries2D.map { $0.promotedTo3D() }
126-
let result = try await generateResult(for: Union(geometries3D + promotedFrom2D), in: environment, context: context)
127-
128-
switch localOptions[ModelOptions.FileFormat3D.self] {
129-
case .threeMF: provider = ThreeMFDataProvider(result: result, options: localOptions)
130-
case .stl: provider = BinarySTLDataProvider(result: result, options: localOptions)
131-
}
132-
133-
} else if geometries2D.count > 0 {
134-
let result = try await generateResult(for: Union(geometries2D), in: environment, context: context)
135-
136-
switch localOptions[ModelOptions.FileFormat2D.self] {
137-
case .threeMF: provider = ThreeMFDataProvider(result: result.promotedTo3D(), options: localOptions)
138-
case .svg: provider = SVGDataProvider(result: result, options: localOptions)
139-
}
140-
} else {
141-
logger.warning("No geometry for model \"\(name)\"")
142-
return nil
111+
let warnings: [BuildWarning]
112+
(provider, warnings) = try await ContinuousClock().measure {
113+
try await directives.build(with: options, in: environment, context: context)
114+
} results: { duration, _ in
115+
logger.debug("Built geometry node tree in \(duration)")
116+
}
117+
118+
for warning in warnings {
119+
logger.warning("⚠️ \(warning.description)")
143120
}
121+
122+
} catch BuildError.noGeometry {
123+
logger.error("No geometry for model \"\(name)\"")
124+
return nil
125+
144126
} catch {
145127
logger.error("Cadova caught an error while evaluating model \"\(name)\":\n🛑 \(error)\n")
146128
return nil
@@ -158,21 +140,6 @@ public struct Model: Sendable {
158140

159141
return fileExisted ? nil : url
160142
}
161-
162-
private func generateResult<D: Dimensionality>(
163-
for geometry: D.Geometry,
164-
in environment: EnvironmentValues,
165-
context: EvaluationContext
166-
) async throws -> BuildResult<D> {
167-
let result = try await ContinuousClock().measure {
168-
try await context.buildModelResult(for: geometry, in: environment)
169-
} results: { duration, _ in
170-
logger.debug("Built geometry node tree in \(duration)")
171-
}
172-
173-
result.printWarnings(modelName: name)
174-
return result
175-
}
176143
}
177144

178145
extension Error {
@@ -184,18 +151,3 @@ extension Error {
184151
}
185152
}
186153
}
187-
188-
fileprivate extension Geometry2D {
189-
func promotedTo3D() -> any Geometry3D {
190-
extruded(height: 0.001)
191-
}
192-
}
193-
194-
fileprivate extension BuildResult {
195-
func printWarnings(modelName: String) {
196-
if hasOnly {
197-
logger.warning("⚠️ Model \"\(modelName)\" uses only() modifier; saving a partial geometry tree")
198-
}
199-
elements[ReferenceState.self].printWarningsAtTopLevel()
200-
}
201-
}

Sources/Cadova/Concrete Layer/Build/Options/FileFormats.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,19 @@ public extension ModelOptions {
3737
internal func combined(with other: Self) -> Self { other }
3838
}
3939
}
40+
41+
extension ModelOptions {
42+
func dataProvider(for result: BuildResult<D3>) -> OutputDataProvider {
43+
switch self[FileFormat3D.self] {
44+
case .threeMF: return ThreeMFDataProvider(result: result, options: self)
45+
case .stl: return BinarySTLDataProvider(result: result, options: self)
46+
}
47+
}
48+
49+
func dataProvider(for result: BuildResult<D2>) -> OutputDataProvider {
50+
switch self[FileFormat2D.self] {
51+
case .threeMF: return ThreeMFDataProvider(result: result.promotedTo3D(), options: self)
52+
case .svg: return SVGDataProvider(result: result, options: self)
53+
}
54+
}
55+
}

Sources/Cadova/Concrete Layer/Build/Options/ModelOptions.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ public struct ModelOptions: Sendable, ExpressibleByArrayLiteral {
2121
}
2222
}
2323

24+
internal extension ModelOptions {
25+
func adding(modelName: String? = nil, defaults: ModelOptions? = nil, directives: [BuildDirective] = []) -> Self {
26+
[
27+
.init(ModelName(name: modelName)),
28+
defaults ?? [],
29+
self,
30+
.init(directives.compactMap(\.options))
31+
]
32+
}
33+
}
34+
2435
internal protocol ModelOptionItem: Sendable {
2536
static var defaultValue: Self { get }
2637
func combined(with other: Self) -> Self

0 commit comments

Comments
 (0)