Skip to content

Commit bc47c0d

Browse files
committed
Merge branch 'dev'
2 parents 3d4c93b + 7e66b94 commit bc47c0d

File tree

21 files changed

+584
-305
lines changed

21 files changed

+584
-305
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let package = Package(
88
.library(name: "Cadova", targets: ["Cadova"]),
99
],
1010
dependencies: [
11-
.package(url: "https://github.com/tomasf/manifold-swift.git", .upToNextMinor(from: "0.2.3")),
11+
.package(url: "https://github.com/tomasf/manifold-swift.git", .upToNextMinor(from: "0.4.0")),
1212
.package(url: "https://github.com/tomasf/ThreeMF.git", .upToNextMinor(from: "0.2.0")),
1313
.package(url: "https://github.com/tomasf/Apus.git", .upToNextMinor(from: "0.1.1")),
1414
],

Sources/Cadova/Abstract Layer/2D/Polygon/Polygon.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,14 @@ public extension Polygon {
7777
Self([.zero, [x, 0], [0, y]])
7878
}
7979
}
80+
81+
public extension ParametricCurve where V == Vector2D {
82+
/// Converts the curve into a filled 2D polygon.
83+
///
84+
/// The curve is sampled and used to create a ``Polygon``, which represents the filled interior of the curve.
85+
///
86+
/// - Returns: A polygon representing the filled shape of the curve.
87+
func filled() -> Polygon {
88+
Polygon(self)
89+
}
90+
}

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-
}

Sources/Cadova/Abstract Layer/Operations/Aligned.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,33 @@ public extension Geometry {
2424
child.translated(bounds.translation(for: .init(merging: alignment)))
2525
}
2626
}
27+
28+
/// Temporarily aligns this geometry while performing operations, then restores its original position.
29+
///
30+
/// This helper aligns the geometry according to the provided alignment options, evaluates `operations` in that
31+
/// aligned coordinate space, and then applies the inverse translation to return the result to its original space.
32+
///
33+
/// - Parameters:
34+
/// - alignment: A list of alignment criteria specifying how the geometry should be aligned before evaluating
35+
/// `operations`.
36+
/// - operations: A builder that produces geometry to be evaluated in the aligned space.
37+
/// - Returns: The resulting geometry mapped back into the original coordinate space.
38+
///
39+
/// Example:
40+
/// ```
41+
/// Rectangle(x: 20, y: 10)
42+
/// .whileAligned(at: .center) {
43+
/// $0.rotated(45°)
44+
/// }
45+
/// ```
46+
///
47+
func whileAligned(
48+
at alignment: D.Alignment...,
49+
@GeometryBuilder<D> do operations: @Sendable @escaping (D.Geometry) -> D.Geometry
50+
) -> D.Geometry {
51+
measuringBounds { child, bounds in
52+
let translation = bounds.translation(for: .init(merging: alignment))
53+
return operations(child.translated(translation)).translated(-translation)
54+
}
55+
}
2756
}

Sources/Cadova/Abstract Layer/Operations/Duplication/RepeatAlong.swift

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ extension Geometry {
2424
///
2525
@GeometryBuilder<D>
2626
public func repeated(along axis: D.Axis, in range: Range<Double>, count: Int) -> D.Geometry {
27-
let step = (range.upperBound - range.lowerBound) / Double(count)
28-
for value in stride(from: range.lowerBound, to: range.upperBound, by: step) {
29-
translated(D.Vector(axis, value: value))
27+
if count > 0 {
28+
let step = (range.upperBound - range.lowerBound) / Double(count)
29+
for value in stride(from: range.lowerBound, to: range.upperBound, by: step) {
30+
translated(D.Vector(axis, value: value))
31+
}
3032
}
3133
}
3234

@@ -40,9 +42,13 @@ extension Geometry {
4042
///
4143
@GeometryBuilder<D>
4244
public func repeated(along axis: D.Axis, in range: ClosedRange<Double>, count: Int) -> D.Geometry {
43-
let step = (range.upperBound - range.lowerBound) / Double(count - 1)
44-
for value in stride(from: range.lowerBound, through: range.upperBound, by: step) {
45-
translated(D.Vector(axis, value: value))
45+
if count > 1 {
46+
let step = (range.upperBound - range.lowerBound) / Double(count - 1)
47+
for value in stride(from: range.lowerBound, through: range.upperBound, by: step) {
48+
translated(D.Vector(axis, value: value))
49+
}
50+
} else if count == 1 {
51+
translated(D.Vector(axis, value: range.lowerBound))
4652
}
4753
}
4854

@@ -102,15 +108,19 @@ extension Geometry {
102108

103109
if cyclically {
104110
let count = Int(floor(rangeLength / (boundsLength + minimumSpacing)))
105-
let step = rangeLength / Double(count)
106-
self.repeated(along: axis, step: step, count: count)
107-
.translated(D.Vector(axis, value: range.lowerBound))
111+
if count > 0 {
112+
let step = rangeLength / Double(count)
113+
self.repeated(along: axis, step: step, count: count)
114+
.translated(D.Vector(axis, value: range.lowerBound))
115+
}
108116
} else {
109117
let availableLength = rangeLength - boundsLength
110118
let count = Int(floor(availableLength / (boundsLength + minimumSpacing)))
111-
let step = availableLength / Double(count)
112-
self.repeated(along: axis, step: step, count: count + 1)
113-
.translated(D.Vector(axis, value: range.lowerBound))
119+
if count > 0 {
120+
let step = availableLength / Double(count)
121+
self.repeated(along: axis, step: step, count: count + 1)
122+
.translated(D.Vector(axis, value: range.lowerBound))
123+
}
114124
}
115125
}
116126
}

Sources/Cadova/Abstract Layer/Operations/Duplication/RepeatAround.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ extension Geometry2D {
3939

4040
@GeometryBuilder2D
4141
public func repeated(in range: ClosedRange<Angle>, count: Int) -> any Geometry2D {
42-
if count > 0 {
42+
if count > 1 {
4343
let step = (range.upperBound - range.lowerBound) / Double(count - 1)
4444
for value in stride(from: range.lowerBound, through: range.upperBound, by: step) {
4545
rotated(value)
4646
}
47+
} else if count == 1 {
48+
rotated(range.lowerBound)
4749
}
4850
}
4951
}
@@ -89,11 +91,13 @@ extension Geometry3D {
8991

9092
@GeometryBuilder3D
9193
public func repeated(around axis: Axis3D, in range: ClosedRange<Angle>, count: Int) -> any Geometry3D {
92-
if count > 0 {
94+
if count > 1 {
9395
let step = (range.upperBound - range.lowerBound) / Double(count - 1)
9496
for value in stride(from: range.lowerBound, through: range.upperBound, by: step) {
9597
rotated(angle: value, axis: axis)
9698
}
99+
} else if count == 1 {
100+
rotated(angle: range.lowerBound, axis: axis)
97101
}
98102
}
99103
}

Sources/Cadova/Abstract Layer/Operations/Filling/Fill.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public extension Geometry2D {
77
/// the external outline.
88
///
99
/// - Returns: A new geometry representing the shape with its holes filled.
10-
func filled() -> any Geometry2D {
10+
func fillingHoles() -> any Geometry2D {
1111
CachedConcreteTransformer(body: self, name: "Cadova.Fill") {
1212
.boolean(.union, with: $0.polygons().map {
1313
D2.Concrete(polygons: [$0], fillRule: .nonZero)

0 commit comments

Comments
 (0)