Skip to content

Commit

Permalink
Merge pull request #7 from tomasf/feature/edge-shape
Browse files Browse the repository at this point in the history
Replace old extrusion methods with new API that uses EdgeProfile
  • Loading branch information
tomasf authored Feb 20, 2024
2 parents e685227 + 3fe60c6 commit dd0ad42
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 168 deletions.
36 changes: 36 additions & 0 deletions Sources/SwiftSCAD/Operations/Extrude/Chamfer.swift
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)
}
}
}
94 changes: 94 additions & 0 deletions Sources/SwiftSCAD/Operations/Extrude/EdgeProfile.swift
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,3 @@ public extension Geometry2D {
.rotated(z: angles.lowerBound)
}
}

public enum ExtrusionZSides {
case top
case bottom
case both
}

public enum ExtrusionMethod {
case layered (height: Double)
case convexHull
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
//
// ExtrudeAlong.swift
// GeoTest
//
// Created by Tomas Franzén on 2021-07-07.
//

import Foundation

struct ExtrudeAlong: CoreGeometry3D {
Expand Down
40 changes: 40 additions & 0 deletions Sources/SwiftSCAD/Operations/Extrude/Fillet.swift
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)
}
}
}
72 changes: 0 additions & 72 deletions Sources/SwiftSCAD/Operations/ExtrudeChamfered.swift

This file was deleted.

78 changes: 0 additions & 78 deletions Sources/SwiftSCAD/Operations/ExtrudeRounded.swift

This file was deleted.

0 comments on commit dd0ad42

Please sign in to comment.