-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from tomasf/feature/edge-shape
Replace old extrusion methods with new API that uses EdgeProfile
- Loading branch information
Showing
8 changed files
with
170 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Foundation | ||
|
||
public extension Geometry2D { | ||
@UnionBuilder3D private func extrudedLayered(height: Double, chamferHeight: Double, chamferDepth: Double, layerHeight: Double) -> any Geometry3D { | ||
let layerCount = Int(ceil(chamferHeight / layerHeight)) | ||
let effectiveChamferHeight = Double(layerCount) * layerHeight | ||
|
||
for l in 0...layerCount { | ||
let z = Double(l) * layerHeight | ||
let inset = Double(l) / Double(layerCount) * chamferDepth | ||
offset(amount: -inset, style: .round) | ||
.extruded(height: height - effectiveChamferHeight + z) | ||
} | ||
} | ||
|
||
@UnionBuilder3D private func extrudedConvex(height: Double, chamferHeight: Double, chamferDepth: Double) -> any Geometry3D { | ||
self | ||
.extruded(height: max(height - chamferHeight, 0.001)) | ||
.adding { | ||
self | ||
.offset(amount: -chamferDepth, style: .round) | ||
.extruded(height: 0.001) | ||
.translated(z: height - 0.001) | ||
} | ||
.convexHull() | ||
} | ||
|
||
@UnionBuilder3D internal func chamferEdgeMask(height: Double, chamferWidth: Double, chamferHeight: Double, method: EdgeProfile.Method) -> any Geometry3D { | ||
switch method { | ||
case .layered (let layerHeight): | ||
extrudedLayered(height: height, chamferHeight: chamferHeight, chamferDepth: chamferWidth, layerHeight: layerHeight) | ||
case .convexHull: | ||
extrudedConvex(height: height, chamferHeight: chamferHeight, chamferDepth: chamferWidth) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import Foundation | ||
|
||
/// The shape of the edge of an extruded shape | ||
public enum EdgeProfile: Equatable { | ||
/// A sharp edge without modification | ||
case sharp | ||
/// A rounded edge | ||
case fillet (radius: Double) | ||
/// A flat chamfered edge | ||
/// - Parameters: | ||
/// - width: The depth of the chamfer in the X/Y axes | ||
/// - topEdge: The depth of the chamfer in the Z axis | ||
case chamfer (width: Double, height: Double) | ||
|
||
/// Methods for building an extruded shape with modified edges | ||
public enum Method { | ||
/// Divide the extrusion into distinct layers with a given thickness. While less elegant and more expensive to render, it is suitable for non-convex shapes. Layers work well for 3D printing, as the printing process inherently occurs in layers. | ||
case layered (height: Double) | ||
/// Create a smooth, non-layered shape. It is often computationally less intensive and results in a more aesthetically pleasing form but only works as expected for convex shapes. | ||
case convexHull | ||
} | ||
} | ||
|
||
public extension EdgeProfile { | ||
/// A 45° chamfered edge | ||
static func chamfer(size: Double) -> EdgeProfile { | ||
.chamfer(width: size, height: size) | ||
} | ||
|
||
/// A chamfered edge with a given width and angle | ||
/// - Parameters: | ||
/// - width: The depth of the chamfer in the X/Y axes | ||
/// - angle: An angle between 0° and 90°, measured from the top of the extrusion | ||
static func chamfer(width: Double, angle: Angle) -> EdgeProfile { | ||
assert((0°..<90°).contains(angle), "Chamfer angle must be between 0° and 90°") | ||
return .chamfer(width: width, height: width * tan(angle)) | ||
} | ||
|
||
/// A chamfered edge with a given height and angle | ||
/// - Parameters: | ||
/// - height: The depth of the chamfer in the Z axis | ||
/// - angle: An angle between 0° and 90°, measured from the top of the extrusion | ||
static func chamfer(height: Double, angle: Angle) -> EdgeProfile { | ||
assert((0°..<90°).contains(angle), "Chamfer angle must be between 0° and 90°") | ||
return .chamfer(width: height / tan(angle), height: height) | ||
} | ||
} | ||
|
||
public extension Geometry2D { | ||
|
||
/// Extrudes a 2D geometry into a 3D shape with chamfers or fillets along the top and/or bottom edges | ||
/// | ||
/// This method allows you to create a 3D shape by extruding the 2D geometry. | ||
/// The method of extrusion can be selected based on the desired characteristics | ||
/// of the resulting shape. | ||
/// | ||
/// - Parameters: | ||
/// - height: The height of the extrusion. | ||
/// - topEdge: The profile of the top edge. | ||
/// - bottomEdge: The profile of the bottom edge. | ||
/// - method: The method of extrusion, either `.layered(thickness:)` or `.convexHull`. | ||
/// - `.layered`: This method divides the extrusion into distinct layers with a given thickness. While less elegant and more expensive to render, it is suitable for non-convex shapes. Layers work well for 3D printing, as the printing process inherently occurs in layers. | ||
/// - `.convexHull`: This method creates a smooth, non-layered shape. It is often computationally less intensive and results in a more aesthetically pleasing form but only works as expected for convex shapes. | ||
/// - Returns: The extruded 3D geometry. | ||
|
||
func extruded(height: Double, topEdge: EdgeProfile = .sharp, bottomEdge: EdgeProfile = .sharp, method: EdgeProfile.Method) -> any Geometry3D { | ||
extruded(height: height) | ||
.intersection { | ||
if topEdge != .sharp { | ||
edgeMask(height: height, edgeProfile: topEdge, method: method) | ||
} | ||
} | ||
.intersection { | ||
if bottomEdge != .sharp { | ||
edgeMask(height: height, edgeProfile: bottomEdge, method: method) | ||
.scaled(z: -1) | ||
.translated(z: height) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private extension Geometry2D { | ||
func edgeMask(height: Double, edgeProfile: EdgeProfile, method: EdgeProfile.Method) -> any Geometry3D { | ||
switch edgeProfile { | ||
case .sharp: | ||
extruded(height: height) | ||
case .fillet (let radius): | ||
filletEdgeMask(height: height, topRadius: radius, method: method) | ||
case .chamfer (let chamferWidth, let chamferHeight): | ||
chamferEdgeMask(height: height, chamferWidth: chamferWidth, chamferHeight: chamferHeight, method: method) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 0 additions & 7 deletions
7
...s/SwiftSCAD/Operations/ExtrudeAlong.swift → ...AD/Operations/Extrude/ExtrudedAlong.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Foundation | ||
|
||
public extension Geometry2D { | ||
@UnionBuilder3D private func extrudedLayered(height: Double, topRadius radius: Double, layerHeight: Double) -> any Geometry3D { | ||
let layerCount = Int(ceil(radius / layerHeight)) | ||
let effectiveRadius = Double(layerCount) * layerHeight | ||
|
||
for l in 0..<layerCount { | ||
let z = Double(l) * layerHeight | ||
offset(amount: (cos(asin(z / radius) as Angle) - 1) * radius, style: .round) | ||
.extruded(height: height - effectiveRadius + z + layerHeight) | ||
} | ||
} | ||
|
||
@UnionBuilder3D private func extrudedConvex(height: Double, topRadius radius: Double) -> any Geometry3D { | ||
EnvironmentReader3D { environment in | ||
let facetsPerRev = environment.facets.facetCount(circleRadius: radius) | ||
let facetCount = max(Int(ceil(Double(facetsPerRev) / 4.0)), 1) | ||
|
||
let slices = (0...facetCount).mapUnion { f in | ||
let angle = (Double(f) / Double(facetCount)) * 90° | ||
let inset = cos(angle) * radius - radius | ||
let zOffset = sin(angle) * radius | ||
offset(amount: inset, style: .round) | ||
.extruded(height: height - radius + zOffset) | ||
} | ||
|
||
return slices.convexHull() | ||
} | ||
} | ||
|
||
@UnionBuilder3D internal func filletEdgeMask(height: Double, topRadius radius: Double, method: EdgeProfile.Method) -> any Geometry3D { | ||
switch method { | ||
case .layered (let layerHeight): | ||
extrudedLayered(height: height, topRadius: radius, layerHeight: layerHeight) | ||
case .convexHull: | ||
extrudedConvex(height: height, topRadius: radius) | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.