From 3a00d6328cfa83e507a7075568ea00097840d83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Franze=CC=81n?= Date: Thu, 29 Aug 2024 15:27:39 +0200 Subject: [PATCH] Add NaturalUpDirection --- .../Environment/NaturalUpDirection.swift | 123 ++++++++++++++++++ .../Operations/Boolean/Intersection.swift | 8 +- .../Shapes/2D/Overhang/Teardrop.swift | 40 +++--- Tests/Tests/SCAD/2dmisc.scad | 2 +- 4 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 Sources/SwiftSCAD/Environment/NaturalUpDirection.swift diff --git a/Sources/SwiftSCAD/Environment/NaturalUpDirection.swift b/Sources/SwiftSCAD/Environment/NaturalUpDirection.swift new file mode 100644 index 0000000..6b4b27e --- /dev/null +++ b/Sources/SwiftSCAD/Environment/NaturalUpDirection.swift @@ -0,0 +1,123 @@ +import Foundation + +internal extension Environment { + private static let key = ValueKey(rawValue: "SwiftSCAD.NaturalUpDirection") + + struct NaturalUpDirectionData { + let direction: Vector3D + let transform: AffineTransform3D + } + + var naturalUpDirectionData: NaturalUpDirectionData? { + self[Self.key] as? NaturalUpDirectionData + } + + func settingNaturalUpDirectionData(_ direction: NaturalUpDirectionData?) -> Environment { + setting(key: Self.key, value: direction) + } +} + +public extension Environment { + /// The natural up direction for the current environment. + /// + /// This computed property returns the up direction as a `Vector3D` if it has been set. + /// The direction is adjusted based on the environment's current transformation. + /// If no up direction is defined, it returns `nil`. + /// + /// The returned vector represents the "natural" up direction in the current orientation, + /// taking into account any transformations that have been applied to the environment. + /// + /// - Returns: A `Vector3D` representing the natural up direction, or `nil` if not set. + /// + var naturalUpDirection: Vector3D? { + naturalUpDirectionData.map { upDirection in + let upTransform = upDirection.transform.inverse.concatenated(with: transform.inverse) + return (upTransform.apply(to: upDirection.direction) - upTransform.apply(to: .zero)).normalized + } + } + + /// The angle of the natural up direction relative to the XY plane, if defined. + /// + /// This computed property returns the angle between the positive X-axis and the + /// projection of the natural up direction onto the XY plane. If the natural up + /// direction is set, the method calculates the angle in the XY plane. If the + /// natural up direction is not set, the method returns `nil`. + /// + /// - Returns: An optional `Angle` representing the angle of the natural up direction + /// in the XY plane, or `nil` if the natural up direction is not set. + /// + var naturalUpDirectionXYAngle: Angle? { + naturalUpDirection.map { Vector2D.zero.angle(to: $0.xy) } + } + + /// Sets the natural up direction relative to the environment's local coordinate system. + /// + /// This method assigns a new natural up direction to the environment, expressed as a `Vector3D`. + /// The direction is specified relative to the environment's current local coordinate system. + /// If the `direction` is `nil`, the natural up direction is cleared, effectively removing + /// any specific orientation considerations from the environment. + /// + /// - Parameter direction: A `Vector3D` representing the new natural up direction, or `nil` to remove it. + /// - Returns: A new `Environment` instance with the updated natural up direction. + /// + func settingNaturalUpDirection(_ direction: Vector3D?) -> Environment { + settingNaturalUpDirectionData(direction.map { + .init(direction: $0, transform: transform) + }) + } +} + +public extension Geometry3D { + /// Sets the natural up direction for the geometry. + /// + /// This method defines the direction that is considered "up" in the natural orientation + /// of the geometry. This is particularly useful for 3D printing applications, where + /// the up direction affects how overhangs are to be compensated for. You can read this value + /// through `Environment.naturalUpDirection`, where it's been transformed to match that + /// coordinate system. + /// + /// - Parameter direction: The `Vector3D` representing the up direction in this geometry's + /// coordinate system. The default value is `.up`. + /// - Returns: A new instance of `Geometry3D` with the natural up direction set in its + /// environment. + /// + /// - See Also: `Teardrop` + /// + func definingNaturalUpDirection(_ direction: Vector3D = .up) -> any Geometry3D { + withEnvironment { environment in + environment.settingNaturalUpDirection(direction) + } + } + + /// Removes the natural up direction for the geometry. + /// + /// This method undefines the previously set natural up direction. + /// This is useful if you want to remove any specific orientation + /// considerations and revert to a state where the natural up + /// direction is undefined. + /// + /// - Returns: A new instance of `Geometry3D` with the natural up direction unset. + /// + func clearingNaturalUpDirection() -> any Geometry3D { + withEnvironment { environment in + environment.settingNaturalUpDirection(nil) + } + } +} + +public extension Geometry2D { + /// Removes the natural up direction for the geometry. + /// + /// This method undefines the previously set natural up direction. + /// This is useful if you want to remove any specific orientation + /// considerations and revert to a state where the natural up + /// direction is undefined. + /// + /// - Returns: A new instance of `Geometry2D` with the natural up direction unset. + /// + func clearingNaturalUpDirection() -> any Geometry2D { + withEnvironment { environment in + environment.settingNaturalUpDirection(nil) + } + } +} diff --git a/Sources/SwiftSCAD/Operations/Boolean/Intersection.swift b/Sources/SwiftSCAD/Operations/Boolean/Intersection.swift index 138d839..1d4bd69 100644 --- a/Sources/SwiftSCAD/Operations/Boolean/Intersection.swift +++ b/Sources/SwiftSCAD/Operations/Boolean/Intersection.swift @@ -41,8 +41,8 @@ public extension Geometry2D { /// - with: The other geometry to intersect with this /// - Returns: The intersection (overlap) of this geometry and the input - func intersection(@UnionBuilder2D with other: () -> any Geometry2D) -> any Geometry2D { - Intersection2D(children: [self, other()]) + func intersection(@SequenceBuilder2D with other: () -> [any Geometry2D]) -> any Geometry2D { + Intersection2D(children: [self] + other()) } func intersection(_ other: any Geometry2D...) -> any Geometry2D { @@ -65,8 +65,8 @@ public extension Geometry3D { /// - with: The other geometry to intersect with this /// - Returns: The intersection (overlap) of this geometry and the input - func intersection(@UnionBuilder3D with other: () -> any Geometry3D) -> any Geometry3D { - Intersection3D(children: [self, other()]) + func intersection(@SequenceBuilder3D with other: () -> [any Geometry3D]) -> any Geometry3D { + Intersection3D(children: [self] + other()) } func intersection(_ other: any Geometry3D...) -> any Geometry3D { diff --git a/Sources/SwiftSCAD/Shapes/2D/Overhang/Teardrop.swift b/Sources/SwiftSCAD/Shapes/2D/Overhang/Teardrop.swift index 1784898..1386be2 100644 --- a/Sources/SwiftSCAD/Shapes/2D/Overhang/Teardrop.swift +++ b/Sources/SwiftSCAD/Shapes/2D/Overhang/Teardrop.swift @@ -2,9 +2,11 @@ import Foundation /// A teardrop shape used for 3D printing. /// -/// A teardrop is commonly used in place of a circle in 3D printing designs where the top of the circle would have a steep overhang. -/// By replacing the circular geometry with a teardrop, it becomes more suitable for printing, especially without support structures. -/// The `bridged` style employs bridging at the top of the originally intended circle, creating a more circle-like shape while still being printable. +/// A teardrop shape is often used in place of a circle in 3D printing designs to reduce steep overhangs that are difficult to print. +/// By replacing circular geometry with a teardrop, the design becomes more suitable for printing, especially without the need for support structures. +/// The `bridged` style introduces bridging at the top of the originally intended circle, resulting in a shape that retains a more circular appearance while remaining printable. +/// +/// If `Environment.naturalUpDirection` is set, the Teardrop will automatically rotate so that its point or bridge faces upward. /// /// ``` /// // Example of creating a teardrop shape with a diameter of 10, angle of 30°, and full style. @@ -34,26 +36,24 @@ public struct Teardrop: Shape2D { let y = sin(angle) * diameter/2 let diagonal = diameter / sin(angle) - let base = Union { + EnvironmentReader { environment in Circle(diameter: diameter) - - Rectangle(diagonal) - .rotated(-angle) - .translated(x: -x, y: y) - .intersection { + .adding { Rectangle(diagonal) - .rotated(angle + 90°) - .translated(x: x, y: y) - } - } + .rotated(-angle) + .translated(x: -x, y: y) + .intersection { + Rectangle(diagonal) + .rotated(angle + 90°) + .translated(x: x, y: y) - if style == .bridged { - return base.intersection { - Rectangle(diameter) - .aligned(at: .center) - } - } else { - return base + if style == .bridged { + Rectangle(diameter) + .aligned(at: .center) + } + } + } + .rotated(environment.naturalUpDirectionXYAngle.map { $0 - 90° } ?? .zero) } } diff --git a/Tests/Tests/SCAD/2dmisc.scad b/Tests/Tests/SCAD/2dmisc.scad index 120587c..b731940 100644 --- a/Tests/Tests/SCAD/2dmisc.scad +++ b/Tests/Tests/SCAD/2dmisc.scad @@ -1 +1 @@ -let($fa=2.000000, $fn=0, $fs=0.150000) union() { offset(r=-0.350000) offset(delta=0.350000) offset(r=0.350000) offset(delta=-0.350000) difference() { translate(v=[0.000000, -5.000000]) square(size=[30.000000, 10.000000]); union() { scale(v=[2.000000, 1.000000]) circle(d=8.000000); translate(v=[15.000000, 0.000000]) polygon(points=[[0.000000, 0.000000], [3.758770, 1.368081], [3.704699, 1.508379], [3.645392, 1.646547], [3.580935, 1.782388], [3.511418, 1.915710], [3.436939, 2.046326], [3.357604, 2.174050], [3.273525, 2.298703], [3.184820, 2.420107], [3.091616, 2.538092], [2.994043, 2.652491], [2.892240, 2.763141], [2.786350, 2.869888], [2.676522, 2.972579], [2.562913, 3.071071], [2.445683, 3.165223], [2.324997, 3.254902], [2.201026, 3.339983], [2.073945, 3.420344], [1.943934, 3.495872], [1.811175, 3.566461], [1.675858, 3.632010], [1.538173, 3.692428], [1.398314, 3.747628], [1.256480, 3.797533], [1.112870, 3.842072], [0.967688, 3.881183], [0.821138, 3.914809], [0.673428, 3.942904], [0.524767, 3.965428], [0.375364, 3.982349], [0.225431, 3.993643], [0.075179, 3.999293], [-0.075179, 3.999293], [-0.225431, 3.993643], [-0.375364, 3.982349], [-0.524767, 3.965428], [-0.673428, 3.942904], [-0.821138, 3.914809], [-0.967688, 3.881183], [-1.112870, 3.842072], [-1.256480, 3.797533], [-1.398314, 3.747628], [-1.538173, 3.692428], [-1.675858, 3.632010], [-1.811175, 3.566461], [-1.943934, 3.495872], [-2.073945, 3.420344], [-2.201026, 3.339983], [-2.324997, 3.254902], [-2.445683, 3.165223], [-2.562913, 3.071071], [-2.676522, 2.972579], [-2.786350, 2.869888], [-2.892240, 2.763141], [-2.994043, 2.652491], [-3.091616, 2.538092], [-3.184820, 2.420107], [-3.273525, 2.298703], [-3.357604, 2.174050], [-3.436939, 2.046326], [-3.511418, 1.915710], [-3.580935, 1.782388], [-3.645392, 1.646547], [-3.704699, 1.508379], [-3.758770, 1.368081]]); translate(v=[22.000000, 0.000000]) union() { circle(d=5.000000); intersection() { translate(v=[-1.767767, 1.767767]) rotate(a=-45.000000) square(size=[7.071068, 7.071068]); translate(v=[1.767767, 1.767767]) rotate(a=135.000000) square(size=[7.071068, 7.071068]); } } translate(v=[27.000000, 0.000000]) intersection() { union() { circle(d=4.000000); intersection() { translate(v=[-1.732051, 1.000000]) rotate(a=-30.000000) square(size=[8.000000, 8.000000]); translate(v=[1.732051, 1.000000]) rotate(a=120.000000) square(size=[8.000000, 8.000000]); } } translate(v=[-2.000000, -2.000000]) square(size=[4.000000, 4.000000]); } } } translate(v=[-3.000000, 0.000000]) rotate(a=45.000000) translate(v=[-5.000000, 0.000000]) intersection() { intersection() { intersection() { square(size=[10.000000, 10.000000]); translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 0.000000], [10.002000, 0.000000], [10.002000, 0.000000], [5.000000, 0.000000], [4.825503, 0.003046], [4.651218, 0.012180], [4.477358, 0.027391], [4.304134, 0.048660], [4.131759, 0.075961], [3.960442, 0.109262], [3.790391, 0.148521], [3.621813, 0.193692], [3.454915, 0.244717], [3.289899, 0.301537], [3.126967, 0.364081], [2.966317, 0.432273], [2.808144, 0.506030], [2.652642, 0.585262], [2.500000, 0.669873], [2.350404, 0.759760], [2.204035, 0.854812], [2.061074, 0.954915], [1.921693, 1.059946], [1.786062, 1.169778], [1.654347, 1.284276], [1.526708, 1.403301], [1.403301, 1.526708], [1.284276, 1.654347], [1.169778, 1.786062], [1.059946, 1.921693], [0.954915, 2.061074], [0.854812, 2.204035], [0.759760, 2.350404], [0.669873, 2.500000], [0.585262, 2.652642], [0.506030, 2.808144], [0.432273, 2.966317], [0.364081, 3.126967], [0.301537, 3.289899], [0.244717, 3.454915], [0.193692, 3.621813], [0.148521, 3.790391], [0.109262, 3.960442], [0.075961, 4.131759], [0.048660, 4.304134], [0.027391, 4.477358], [0.012180, 4.651218], [0.003046, 4.825503], [0.000000, 5.000000]]); } translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 3.000000], [9.998386, 2.852797], [9.987554, 2.705949], [9.969530, 2.559809], [9.944356, 2.414729], [9.912094, 2.271059], [9.872821, 2.129146], [9.826632, 1.989330], [9.773639, 1.851950], [9.713968, 1.717335], [9.647764, 1.585810], [9.575186, 1.457692], [9.496409, 1.333289], [9.411623, 1.212902], [9.321031, 1.096820], [9.224853, 0.985323], [9.123320, 0.878680], [9.016677, 0.777147], [8.905180, 0.680969], [8.789098, 0.590377], [8.668711, 0.505591], [8.544308, 0.426814], [8.416190, 0.354236], [8.284665, 0.288032], [8.150050, 0.228361], [8.012670, 0.175368], [7.872854, 0.129179], [7.730941, 0.089906], [7.587271, 0.057644], [7.442191, 0.032470], [7.296051, 0.014446], [7.149203, 0.003614], [7.002000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000]]); } translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [8.002000, 10.002000], [8.151460, 9.996408], [8.300085, 9.979662], [8.447042, 9.951856], [8.591510, 9.913146], [8.732682, 9.863747], [8.869767, 9.803938], [9.002000, 9.734051], [9.128640, 9.654478], [9.248980, 9.565663], [9.362345, 9.468104], [9.468104, 9.362345], [9.565663, 9.248980], [9.654478, 9.128640], [9.734051, 9.002000], [9.803938, 8.869767], [9.863747, 8.732682], [9.913146, 8.591510], [9.951856, 8.447042], [9.979662, 8.300085], [9.996408, 8.151460], [10.002000, 8.002000], [10.002000, 0.000000], [10.002000, 0.000000], [10.002000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000]]); } multmatrix(m=[[1.000000, 0.363970, 0.000000, 0.000000], [0.000000, 1.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.000000, 0.000000], [0.000000, 0.000000, 0.000000, 1.000000]]) translate(v=[0.000000, 5.000000]) offset(delta=0.400000) text(font="Helvetica", halign="left", size=10.080000, text="SwiftSCAD", valign="bottom"); translate(v=[50.000000, -10.000000]) union() { rotate(a=20.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=66.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=112.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=158.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=204.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } } } +let($fa=2.000000, $fn=0, $fs=0.150000) union() { offset(r=-0.350000) offset(delta=0.350000) offset(r=0.350000) offset(delta=-0.350000) difference() { translate(v=[0.000000, -5.000000]) square(size=[30.000000, 10.000000]); union() { scale(v=[2.000000, 1.000000]) circle(d=8.000000); translate(v=[15.000000, 0.000000]) polygon(points=[[0.000000, 0.000000], [3.758770, 1.368081], [3.704699, 1.508379], [3.645392, 1.646547], [3.580935, 1.782388], [3.511418, 1.915710], [3.436939, 2.046326], [3.357604, 2.174050], [3.273525, 2.298703], [3.184820, 2.420107], [3.091616, 2.538092], [2.994043, 2.652491], [2.892240, 2.763141], [2.786350, 2.869888], [2.676522, 2.972579], [2.562913, 3.071071], [2.445683, 3.165223], [2.324997, 3.254902], [2.201026, 3.339983], [2.073945, 3.420344], [1.943934, 3.495872], [1.811175, 3.566461], [1.675858, 3.632010], [1.538173, 3.692428], [1.398314, 3.747628], [1.256480, 3.797533], [1.112870, 3.842072], [0.967688, 3.881183], [0.821138, 3.914809], [0.673428, 3.942904], [0.524767, 3.965428], [0.375364, 3.982349], [0.225431, 3.993643], [0.075179, 3.999293], [-0.075179, 3.999293], [-0.225431, 3.993643], [-0.375364, 3.982349], [-0.524767, 3.965428], [-0.673428, 3.942904], [-0.821138, 3.914809], [-0.967688, 3.881183], [-1.112870, 3.842072], [-1.256480, 3.797533], [-1.398314, 3.747628], [-1.538173, 3.692428], [-1.675858, 3.632010], [-1.811175, 3.566461], [-1.943934, 3.495872], [-2.073945, 3.420344], [-2.201026, 3.339983], [-2.324997, 3.254902], [-2.445683, 3.165223], [-2.562913, 3.071071], [-2.676522, 2.972579], [-2.786350, 2.869888], [-2.892240, 2.763141], [-2.994043, 2.652491], [-3.091616, 2.538092], [-3.184820, 2.420107], [-3.273525, 2.298703], [-3.357604, 2.174050], [-3.436939, 2.046326], [-3.511418, 1.915710], [-3.580935, 1.782388], [-3.645392, 1.646547], [-3.704699, 1.508379], [-3.758770, 1.368081]]); translate(v=[22.000000, 0.000000]) rotate(a=0.000000) union() { circle(d=5.000000); intersection() { translate(v=[-1.767767, 1.767767]) rotate(a=-45.000000) square(size=[7.071068, 7.071068]); translate(v=[1.767767, 1.767767]) rotate(a=135.000000) square(size=[7.071068, 7.071068]); } } translate(v=[27.000000, 0.000000]) rotate(a=0.000000) union() { circle(d=4.000000); intersection() { translate(v=[-1.732051, 1.000000]) rotate(a=-30.000000) square(size=[8.000000, 8.000000]); translate(v=[1.732051, 1.000000]) rotate(a=120.000000) square(size=[8.000000, 8.000000]); translate(v=[-2.000000, -2.000000]) square(size=[4.000000, 4.000000]); } } } } translate(v=[-3.000000, 0.000000]) rotate(a=45.000000) translate(v=[-5.000000, 0.000000]) intersection() { intersection() { intersection() { square(size=[10.000000, 10.000000]); translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 0.000000], [10.002000, 0.000000], [10.002000, 0.000000], [5.000000, 0.000000], [4.825503, 0.003046], [4.651218, 0.012180], [4.477358, 0.027391], [4.304134, 0.048660], [4.131759, 0.075961], [3.960442, 0.109262], [3.790391, 0.148521], [3.621813, 0.193692], [3.454915, 0.244717], [3.289899, 0.301537], [3.126967, 0.364081], [2.966317, 0.432273], [2.808144, 0.506030], [2.652642, 0.585262], [2.500000, 0.669873], [2.350404, 0.759760], [2.204035, 0.854812], [2.061074, 0.954915], [1.921693, 1.059946], [1.786062, 1.169778], [1.654347, 1.284276], [1.526708, 1.403301], [1.403301, 1.526708], [1.284276, 1.654347], [1.169778, 1.786062], [1.059946, 1.921693], [0.954915, 2.061074], [0.854812, 2.204035], [0.759760, 2.350404], [0.669873, 2.500000], [0.585262, 2.652642], [0.506030, 2.808144], [0.432273, 2.966317], [0.364081, 3.126967], [0.301537, 3.289899], [0.244717, 3.454915], [0.193692, 3.621813], [0.148521, 3.790391], [0.109262, 3.960442], [0.075961, 4.131759], [0.048660, 4.304134], [0.027391, 4.477358], [0.012180, 4.651218], [0.003046, 4.825503], [0.000000, 5.000000]]); } translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 10.002000], [10.002000, 3.000000], [9.998386, 2.852797], [9.987554, 2.705949], [9.969530, 2.559809], [9.944356, 2.414729], [9.912094, 2.271059], [9.872821, 2.129146], [9.826632, 1.989330], [9.773639, 1.851950], [9.713968, 1.717335], [9.647764, 1.585810], [9.575186, 1.457692], [9.496409, 1.333289], [9.411623, 1.212902], [9.321031, 1.096820], [9.224853, 0.985323], [9.123320, 0.878680], [9.016677, 0.777147], [8.905180, 0.680969], [8.789098, 0.590377], [8.668711, 0.505591], [8.544308, 0.426814], [8.416190, 0.354236], [8.284665, 0.288032], [8.150050, 0.228361], [8.012670, 0.175368], [7.872854, 0.129179], [7.730941, 0.089906], [7.587271, 0.057644], [7.442191, 0.032470], [7.296051, 0.014446], [7.149203, 0.003614], [7.002000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000]]); } translate(v=[-0.001000, -0.001000]) polygon(points=[[0.000000, 10.002000], [0.000000, 10.002000], [0.000000, 10.002000], [8.002000, 10.002000], [8.151460, 9.996408], [8.300085, 9.979662], [8.447042, 9.951856], [8.591510, 9.913146], [8.732682, 9.863747], [8.869767, 9.803938], [9.002000, 9.734051], [9.128640, 9.654478], [9.248980, 9.565663], [9.362345, 9.468104], [9.468104, 9.362345], [9.565663, 9.248980], [9.654478, 9.128640], [9.734051, 9.002000], [9.803938, 8.869767], [9.863747, 8.732682], [9.913146, 8.591510], [9.951856, 8.447042], [9.979662, 8.300085], [9.996408, 8.151460], [10.002000, 8.002000], [10.002000, 0.000000], [10.002000, 0.000000], [10.002000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000], [0.000000, 0.000000]]); } multmatrix(m=[[1.000000, 0.363970, 0.000000, 0.000000], [0.000000, 1.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.000000, 0.000000], [0.000000, 0.000000, 0.000000, 1.000000]]) translate(v=[0.000000, 5.000000]) offset(delta=0.400000) text(font="Helvetica", halign="left", size=10.080000, text="SwiftSCAD", valign="bottom"); translate(v=[50.000000, -10.000000]) union() { rotate(a=20.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=66.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=112.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=158.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } rotate(a=204.000000) translate(v=[15.000000, 0.000000]) difference() { circle(d=10.000000); union() { rotate(a=0.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=120.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); rotate(a=240.000000) translate(v=[3.000000, 0.000000]) translate(v=[0.000000, -5.000000]) square(size=[10.000000, 10.000000]); } } } }