From 91793208cafc37c74a9df39491fba2aa9b3268f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Franze=CC=81n?= Date: Mon, 21 Oct 2024 23:52:44 +0200 Subject: [PATCH] Improve path builders --- .../Path Builder/BezierPath.Builder.swift | 64 ++++++---------- .../Path Builder/BezierPath.Component.swift | 40 ---------- .../Path Builder/ComponentFunctions.swift | 40 +++++----- .../Path Builder/PathBuilderValue.swift | 73 +++++++++++++++++++ .../Values/Vectors/DimensionalValue.swift | 18 +++-- .../Values/Vectors/OptionalVector.swift | 26 ------- Tests/Tests/BezierPathBuilder.swift | 10 ++- 7 files changed, 134 insertions(+), 137 deletions(-) delete mode 100644 Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Component.swift create mode 100644 Sources/SwiftSCAD/Values/Bezier/Path Builder/PathBuilderValue.swift delete mode 100644 Sources/SwiftSCAD/Values/Vectors/OptionalVector.swift diff --git a/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Builder.swift b/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Builder.swift index c203d2a..80c35f6 100644 --- a/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Builder.swift +++ b/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Builder.swift @@ -1,56 +1,40 @@ import Foundation public extension BezierPath { - @resultBuilder struct Builder { - public typealias Component = BezierPath.Component + typealias Builder = ArrayBuilder.Component> - public static func buildExpression(_ expression: Component) -> [Component] { - [expression] - } - - public static func buildBlock(_ children: [Component]...) -> [Component] { - children.flatMap { $0 } - } + init(from: V = .zero, mode defaultMode: PathBuilderPositioning = .absolute, @Builder builder: () -> [Component]) { + var start = from + self.init(startPoint: from, curves: builder().flatMap { + $0.bezierCurves(start: &start, defaultMode: defaultMode) + }) + } - // If without else - public static func buildOptional(_ children: [Component]?) -> [Component] { - if let children { - [.init(group: children)] - } else { - [] - } - } + struct Component { + private let points: [PathBuilderVector] - // If - public static func buildEither(first child: [Component]) -> [Component] { - [.init(group: child)] + internal init(_ points: [PathBuilderVector]) { + self.points = points } - // Else - public static func buildEither(second child: [Component]) -> [Component] { - [.init(group: child)] + internal func bezierCurves(start: inout V, defaultMode: PathBuilderPositioning) -> [Curve] { + let controlPoints = [start] + points.map { + $0.value(relativeTo: start, defaultMode: defaultMode) + } + start = controlPoints.last! + return [Curve(controlPoints: controlPoints)] } - // Loops - public static func buildArray(_ children: [[Component]]) -> [Component] { - children.flatMap { $0 } + internal func withDefaultMode(_ mode: PathBuilderPositioning) -> Self { + .init(points.map { $0.withDefaultMode(mode) }) } - public static func buildExpression(_ void: Void) -> [Component] { [] } - public static func buildExpression(_ never: Never) -> [Component] {} + public var relative: Component { withDefaultMode(.relative) } + public var absolute: Component { withDefaultMode(.absolute) } } } -public extension BezierPath { - enum BuilderPositioning { - case relative - case absolute - } - - init(from: V = .zero, _ positioning: BuilderPositioning = .absolute, @Builder builder: () -> [Component]) { - var start = from - self.init(startPoint: from, curves: builder().flatMap { - $0.bezierCurves(start: &start, positioning: positioning) - }) - } +public enum PathBuilderPositioning: Sendable { + case absolute + case relative } diff --git a/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Component.swift b/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Component.swift deleted file mode 100644 index b64df59..0000000 --- a/Sources/SwiftSCAD/Values/Bezier/Path Builder/BezierPath.Component.swift +++ /dev/null @@ -1,40 +0,0 @@ -extension BezierPath { - public struct Component { - private enum Contents { - case points ([OptionalVector]) - case subcomponents ([Component]) - } - private let contents: Contents - - internal init(_ points: [OptionalVector]) { - contents = .points(points) - } - - internal init(group: [Component]) { - contents = .subcomponents(group) - } - - internal func bezierCurves(start: inout V, positioning: BezierPath.BuilderPositioning) -> [Curve] { - switch contents { - case .points (let points): - let controlPoints: [V] - switch positioning { - case .relative: - controlPoints = [start] + points.map { $0.vector(with: .zero) + start } - - case .absolute: - controlPoints = [start] + points.map { $0.vector(with: start) } - } - - start = controlPoints.last! - return [Curve(controlPoints: controlPoints)] - - case .subcomponents (let components): - var localStart = start - return components.flatMap { - $0.bezierCurves(start: &localStart, positioning: positioning) - } - } - } - } -} diff --git a/Sources/SwiftSCAD/Values/Bezier/Path Builder/ComponentFunctions.swift b/Sources/SwiftSCAD/Values/Bezier/Path Builder/ComponentFunctions.swift index 5673632..18d6a5b 100644 --- a/Sources/SwiftSCAD/Values/Bezier/Path Builder/ComponentFunctions.swift +++ b/Sources/SwiftSCAD/Values/Bezier/Path Builder/ComponentFunctions.swift @@ -5,33 +5,29 @@ public func line(_ point: V) -> BezierPath.Component { } public func curve(_ controlPoints: [V]) -> BezierPath.Component { - .init(controlPoints.map(OptionalVector.init)) + .init(controlPoints.map(PathBuilderVector.init)) } -// MARK: - Groups - -public func group(@BezierPath.Builder builder: () -> [BezierPath.Component]) -> BezierPath.Component { - .init(group: builder()) -} - - // MARK: - 2D -public func line(x: Double? = nil, y: Double? = nil) -> BezierPath2D.Component { +public func line( + x: any PathBuilderValue = .unchanged, + y: any PathBuilderValue = .unchanged +) -> BezierPath2D.Component { .init([.init(x, y)]) } public func curve( - controlX x1: Double, controlY y1: Double, - endX: Double, endY: Double + controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, + endX: any PathBuilderValue, endY: any PathBuilderValue ) -> BezierPath2D.Component { .init([.init(x1, y1), .init(endX, endY)]) } public func curve( - controlX x1: Double, controlY y1: Double, - controlX x2: Double, controlY y2: Double, - endX: Double, endY: Double + controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, + controlX x2: any PathBuilderValue, controlY y2: any PathBuilderValue, + endX: any PathBuilderValue, endY: any PathBuilderValue ) -> BezierPath2D.Component { .init([.init(x1, y1), .init(x2, y2), .init(endX, endY)]) } @@ -39,21 +35,25 @@ public func curve( // MARK: - 3D -public func line(x: Double? = nil, y: Double? = nil, z: Double? = nil) -> BezierPath3D.Component { +public func line( + x: any PathBuilderValue = .unchanged, + y: any PathBuilderValue = .unchanged, + z: any PathBuilderValue = .unchanged +) -> BezierPath3D.Component { .init([.init(x, y, z)]) } public func curve( - controlX x1: Double, controlY y1: Double, controlZ z1: Double, - endX: Double, endY: Double, endZ: Double + controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, controlZ z1: any PathBuilderValue, + endX: any PathBuilderValue, endY: any PathBuilderValue, endZ: any PathBuilderValue ) -> BezierPath3D.Component { .init([.init(x1, y1, z1), .init(endX, endY, endZ)]) } public func curve( - controlX x1: Double, controlY y1: Double, controlZ z1: Double, - controlX x2: Double, controlY y2: Double, controlZ z2: Double, - endX: Double, endY: Double, endZ: Double + controlX x1: any PathBuilderValue, controlY y1: any PathBuilderValue, controlZ z1: any PathBuilderValue, + controlX x2: any PathBuilderValue, controlY y2: any PathBuilderValue, controlZ z2: any PathBuilderValue, + endX: any PathBuilderValue, endY: any PathBuilderValue, endZ: any PathBuilderValue ) -> BezierPath3D.Component { .init([.init(x1, y1, z1), .init(x2, y2, z2), .init(endX, endY, endZ)]) } diff --git a/Sources/SwiftSCAD/Values/Bezier/Path Builder/PathBuilderValue.swift b/Sources/SwiftSCAD/Values/Bezier/Path Builder/PathBuilderValue.swift new file mode 100644 index 0000000..74631db --- /dev/null +++ b/Sources/SwiftSCAD/Values/Bezier/Path Builder/PathBuilderValue.swift @@ -0,0 +1,73 @@ +import Foundation + +public protocol PathBuilderValue: Sendable {} + +extension Double: PathBuilderValue { + public var relative: any PathBuilderValue { + PositionedValue(value: self, mode: .relative) + } + + public var absolute: any PathBuilderValue { + PositionedValue(value: self, mode: .absolute) + } +} + +public extension PathBuilderValue where Self == Double { + static var unchanged: any PathBuilderValue { 0.relative } +} + +internal struct PositionedValue: PathBuilderValue { + var value: Double + var mode: PathBuilderPositioning? + + func value(relativeTo base: Double, defaultMode: PathBuilderPositioning) -> Double { + switch mode ?? defaultMode { + case .relative: base + value + case .absolute: value + } + } + + func withDefaultMode(_ defaultMode: PathBuilderPositioning) -> PositionedValue { + .init(value: value, mode: mode ?? defaultMode) + } +} + +internal extension PathBuilderValue { + var positionedValue: PositionedValue { + if let self = self as? Double { + PositionedValue(value: self, mode: nil) + } else if let self = self as? PositionedValue { + self + } else { + preconditionFailure("Unknown BezierBuilderValue type.") + } + } +} + +internal typealias PathBuilderVector = DimensionalValue + +internal extension PathBuilderVector where Element == PositionedValue { + init(_ vector: V) { + self.init { + PositionedValue(value: vector[$0], mode: nil) + } + } + + init(_ x: any PathBuilderValue, _ y: any PathBuilderValue) where V == Vector2D { + self.init(x: x.positionedValue, y: y.positionedValue) + } + + init(_ x: any PathBuilderValue, _ y: any PathBuilderValue, _ z: any PathBuilderValue) where V == Vector3D { + self.init(x: x.positionedValue, y: y.positionedValue, z: z.positionedValue) + } + + func withDefaultMode(_ mode: PathBuilderPositioning) -> Self { + map { $1.withDefaultMode(mode) } + } + + func value(relativeTo base: V, defaultMode: PathBuilderPositioning) -> V { + map { + $1.value(relativeTo: base[$0], defaultMode: defaultMode) + }.vector + } +} diff --git a/Sources/SwiftSCAD/Values/Vectors/DimensionalValue.swift b/Sources/SwiftSCAD/Values/Vectors/DimensionalValue.swift index d099525..fa91fd9 100644 --- a/Sources/SwiftSCAD/Values/Vectors/DimensionalValue.swift +++ b/Sources/SwiftSCAD/Values/Vectors/DimensionalValue.swift @@ -1,7 +1,7 @@ import Foundation -internal struct DimensionalValue: Equatable, Sendable { - private enum Value: Equatable { +internal struct DimensionalValue: Sendable { + private enum Value { case xy (Element, Element) case xyz (Element, Element, Element) } @@ -43,12 +43,8 @@ internal struct DimensionalValue: Equa value = .xyz(x, y, z) } - func map(_ operation: (Element) -> New) -> DimensionalValue { - .init(elements.map(operation)) - } - - func mapVector(_ operation: (Int, Element) -> Double) -> V { - .init(elements: elements.enumerated().map(operation)) + func map(_ operation: (_ index: Int, _ element: Element) -> New) -> DimensionalValue { + .init(elements.enumerated().map(operation)) } subscript(_ index: Int) -> Element { @@ -59,3 +55,9 @@ internal struct DimensionalValue: Equa elements.contains(where: predicate) } } + +extension DimensionalValue where Element == Double { + var vector: V { + .init(elements: elements) + } +} diff --git a/Sources/SwiftSCAD/Values/Vectors/OptionalVector.swift b/Sources/SwiftSCAD/Values/Vectors/OptionalVector.swift deleted file mode 100644 index 6c7b080..0000000 --- a/Sources/SwiftSCAD/Values/Vectors/OptionalVector.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -internal typealias OptionalVector = DimensionalValue - -extension OptionalVector where Element == Double? { - func vector(with defaults: V) -> V { - mapVector { index, value in - value ?? defaults[index] - } - } - - init(_ vector: V) { - self.init(vector.elements) - } -} - -extension OptionalVector { - init(x: Double?, y: Double?) { - self.init(x, y) - } -} -extension OptionalVector { - init(x: Double?, y: Double?, z: Double?) { - self.init(x, y, z) - } -} diff --git a/Tests/Tests/BezierPathBuilder.swift b/Tests/Tests/BezierPathBuilder.swift index 66c90b6..ccf78d0 100644 --- a/Tests/Tests/BezierPathBuilder.swift +++ b/Tests/Tests/BezierPathBuilder.swift @@ -3,7 +3,7 @@ import Testing struct BezierPathBuilderTests { @Test func testAbsolute() { - let builderPath = BezierPath2D(from: [10, 4], .absolute) { + let builderPath = BezierPath2D(from: [10, 4]) { line(x: 22, y: 1) line(x: 2) line(y: 76) @@ -31,10 +31,13 @@ struct BezierPathBuilderTests { } @Test func testRelative() { - let builderPath = BezierPath2D(from: [10, 4], .relative) { + let builderPath = BezierPath2D(from: [10, 4], mode: .relative) { line(x: 22, y: 1) line(x: 2) - line(y: 76) + line(y: 74) + if true { + line(y: 2) + } curve( controlX: 7, controlY: 12, endX: 77, endY: 18 @@ -50,6 +53,7 @@ struct BezierPathBuilderTests { let manualPath = BezierPath2D(startPoint: [10, 4]) .addingLine(to: [32, 5]) .addingLine(to: [34, 5]) + .addingLine(to: [34, 79]) .addingLine(to: [34, 81]) .addingQuadraticCurve(controlPoint: [41, 93], end: [111, 99]) .addingLine(to: [111, 99])