Exit Full View
Up

/Phone/PhoneTiltStand.foocad

PhoneTiltStand
FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*

/**
A tilting stand for a phone.
Can be tilted forwards or backwards, but to tilt forwards, the tall fins
will partially obscure the screen.

3 pieces in PLA : ring, stand, fins
3 pieces in TPU : finRubber (x2), finRubberFront
The TPU pieces could be replaced by felt.

In the default view, the red sphere is the center of rotation.
Ideally, for maximum stability, this should also be the center of gravity of the phone.

Print Notes
===========

`fins` needs a high `fillDensity`.
    However, superslicer is still making part of the fins hollow.
    As a workaround, `perimeters` = 20 (or solid layers = 10,000)
    Either will make a solid print.
*/
class PhoneTiltStand : Model {

    @Custom( about="How high up is the center of gravity?" )
    var sphereRadius = 80

    @Custom
    var standHeight = 19

    @Custom( about="Outer radius of the ring" )
    var ringRadius = 35

    @Custom( about="How much smaller is the inner radius compared to ringRadius" )
    var ringThickness = 8

    @Custom( about="Height of the ring" )
    var ringHeight = 8

    @Custom( about="The thickness of the plate that the fins sit on" )
    var plateThickness = 2

    @Custom
    var finSize = Vector3( 10, 20, 60 )

    @Custom
    var finTopThickness = 5

    @Custom( about= "X Offset for the outer pair of fins" )
    var finOffset = 24

    @Custom
    var phoneThickness = 11.8

    @Custom( about="Thickness of the rubber to protect the phone" )
    var backRubberThickness = 1.2

    // This can be customised later to adjust for different phone thicknesses.
    @Custom( about="Thickness of the front rubber" )
    var frontRubberThickness = 1.2

    @Custom( about = "Degrees (preview only)" )
    val tilt = -20

    fun ringProfile() : Shape2d {
        val square = Square( ringThickness, ringHeight ).roundAllCorners(1)
            .rightTo( ringRadius )
        val circle = Circle( sphereRadius ).sides(300)
            .frontTo( 1 ) // Ensure the stand doesn't touch the table
        return square - circle
    }

    @Piece
    fun ring() : Shape3d {
        return ringProfile().revolve().sides(80)
    }

    fun standProfile() : Shape2d {
        val halfCircle = Circle(ringRadius) intersection (Square(ringRadius*3).centerX())
        return (
            halfCircle.scaleY(1.2) +
            halfCircle.mirrorY().scaleY(0.8)
        ).translateY(8)
    }

    @Piece
    fun stand() : Shape3d {
        val sphere = Sphere( sphereRadius ).sides(150)
            .topTo( standHeight )
        val block = standProfile().extrude( standHeight )
        return sphere intersection block
    }

    fun fin(finSize : Vector3, scale : double) : Shape3d {
        val fatFin = Square( finSize.x, finSize.z )
            .roundCorner(3,finSize.x/2)
            .roundCorner(2,finSize.x/2)
            .extrude( finSize.y )
            .rotateX(90)
            .centerX()
        // Cut out a 1/4 cylinder, so that the fin gets progressively shallower towards the top.
        val cylinderX = Circle( finSize.z ).sides(80)
            .scaleY((finSize.y-finTopThickness) / finSize.z)
            .extrude(finSize.x+1, Vector2(scale, 1) ) // Adds an extra curve.
            .rotateY(90)
            .centerX()
            .translateZ( finSize.z )
        return (fatFin - cylinderX) //+ cylinderX.previewOnly()
    }

    @Piece
    @Slice 
    fun fins() : Shape3d {
        val base = standProfile().extrude(plateThickness)

        val middleFin = fin(Vector3(finSize.x, 10, 10), 1)
            .frontTo(phoneThickness /2 + frontRubberThickness)
            .mirrorY()
        val outerFins = fin(finSize, 1.5)
            .frontTo(phoneThickness /2 + backRubberThickness + 1)
            .translateX(finOffset)
            .mirrorX().also()
        return base + (middleFin + outerFins).bottomTo(base.top)
    }

    fun finRubber(height : double, thickness : double) : Shape3d {
        val chamfer = thickness*0.3
        return ExtrusionBuilder().apply {
            crossSection( Square( finSize.x, height ).centerX()
                .roundCorner(3,finSize.x/2)
                .roundCorner(2,finSize.x/2)
            )
            .forward( thickness/2 )
            .crossSection( Square( finSize.x, height - chamfer*0.8).centerX()
                .roundCorner(3,finSize.x/2)
                .roundCorner(2,finSize.x/2)
            )
            .forward( thickness/2 )
            .crossSection( Square( finSize.x, height - chamfer*3).centerX()
                .roundCorner(3,finSize.x/2)
                .roundCorner(2,finSize.x/2)
            )

        }.build()
    }

    @Piece
    fun finRubber() = finRubber( finSize.z, backRubberThickness )

    @Piece
    fun finRubberFront() = finRubber( 10, frontRubberThickness)

    override fun build() : Shape3d {
        val theStand = stand().mirrorZ().color("Green")
        val theFins = fins().color("LightGreen")
        val phone = Cube( 170, phoneThickness, 80 ).previewOnly().centerXY().translateZ(1)
        val phone2 = Cube( 80, phoneThickness, 170 ).previewOnly().centerXY().translateZ(1)
        val centerOfGravity = Sphere(10).translateZ( sphereRadius ).color("red")

        val gap = 0.1
        val movable = (phone + phone2 + centerOfGravity + theStand + theFins)
            .translateZ( - sphereRadius + standHeight+1 + gap)
            .rotateX( tilt )
            .translateZ( sphereRadius )

        return ring() + movable
    }
}