Exit Full View
ZipUp

/Furniture/TrippleLightShade.foocad

TrippleLightShade

There are two flavours.

1. Arms are connected directly to the ceiling rose.
2. Arms are connected to a sphere which is connected to the rose via a thickn stem.

The former is useful for low ceilings.

Naming :

Rose : The visible part nearest to the ceiling.
    Piece `rose` has arms connected directly to it.
    Piece `stemRose` uses a stem and a ball, and the arms are connected to the ball.

Rose Fixture :  Screwed to the ceiling (hidden by the rose)
Pin : 3 tiny pieces to join the rose to the roseFixture.

Ball : )Optional) When the Arms are not connected directly to the Rose
Stem : A rod from the Rose to the Ball
Arm : A rod from the Ball or Rose to the light fitting.
Arm End : Screws into the the Arm
FooCAD Source Code
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import uk.co.nickthecoder.foocad.screws.v3.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*
import uk.co.nickthecoder.foocad.threaded.v2.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class TrippleLightShade : Model {

    @Custom( about="Internal diameter of the ceiling rose" )
    var roseDiameter = 110

    @Custom( about="Height of piece `roseFixture`" )
    var rimHeight = 10

    @Custom( about="Height of part `stemRose`" )
    var stemRoseHeight = 30

    @Custom( about="Rounded radius of `stemRose`" )
    var stemRoseFillet = 10

    @Custom( about="Height of extra support for screws" )
    var screwStubHeight = 5

    @Custom( about="Diameter of holes for wires" )
    var wireDiameter = 7

    @Custom( about="Thickness of most flat surfaces" )
    var thickness = 3

    @Custom( about="Length of piece `arm`" )
    var armLength = 40

    @Custom( about="Angle of arms from part ballB/roundedRose" )
    var armAngle = 45

    @Custom( about="Arm holes from parts ballB/roundedRose" )
    var armCount = 3

    @Custom( about="Angle of arms from part ballA (i.e. pointing upwards)" )
    var armAngleHigh = 50

    @Custom( about="Arm holes from part ballA" )
    var armCountHigh = 0

    @Custom( about="Length and diameter of the step from `stemRose` to `ballA`" )
    var stemSize = Vector2( 22, 80 )

    @Custom( about="Diameter of parts `ballA` and `ballB`" )
    var ballDiameter = 70

    @Custom( about="Should the arms screw in place (If false requires extra 'nut' pieces)" )
    var screwInArms = false

    // Thread to screw arms into the rose/ball as well as the stem into the stemRose.
    // Must be large enough for wires to fit inside.
    meth rodThread()  : Thread {
        val diameter = wireDiameter + thickness*2 + 2.5
        return Thread( diameter, 3 )
            .rodChamfer(0.7)
    }

    // Threads to screw the two parts of the ball together (i.e. pieces `ballA` and `ballB`)
    meth ballThread() : Thread {
        val diameter = ballDiameter - thickness*2 - 2
        return Thread( diameter, 3 )
            .rodChamfer(0.7)
    }

    meth roseScrewIn() : Shape3d {
        val r = roseDiameter/2
        val fins = CircularArc( r + thickness-0.2, r-thickness/2, 30, 60 )
            .smartExtrude( rimHeight/2 )
            .outsideTop( Chamfer(thickness/2) )
            .topTo( rimHeight/2 )
            .repeatAroundZ(6)

        return fins
    }

    @Piece( printable=false )
    meth roseAssembly() = stemRose().color("Green").translateZ(0.1) + roseFixture().translateZ(1)


    // Part of `roseFixture`
    // For cable-ties to hold a transformer (if the lights are low voltage).
    meth holdDown() : Shape3d {
        val block = Square( 30, 14 ).center()
            .smartExtrude( 6 ).top(Chamfer(2))
        val gap = Square( 50, 6 ).center()
            .extrude( 3 ).bottomTo(0.6)
        return block remove gap

    }

    // Part of `roseFixture`
    meth screwHole() : Shape3d {
        return (Cylinder( screwStubHeight, 6 ).remove(Countersink().topTo(screwStubHeight)))
    }

    @Piece( about="A hidden part screwed to the ceiling to support the rose" )
    meth roseFixture() : Shape3d {
        val holeR = 25
        val r = roseDiameter/2 - thickness/2 - 0.3 
        val rim = (Circle( r ) - Circle( r - thickness ))
            .extrude( rimHeight )

        val base = (Circle( r ) - Circle( holeR ))
            .smartExtrude( thickness )
            .insideTop( Chamfer(1))


        val screwHole = screwHole()
        val screwHolesA = screwHole
                .translate( holeR+4, 15, 0 )
                .mirrorY().also().withCavities().mirrorX().also().withCavities()
        val screwHolesB = screwHole
                .translate( r - 12, 15, 0 )
                .mirrorY().also().withCavities().mirrorX().also().withCavities()
        val screwHolesC = screwHole
                .translate( 0, r - 10, 0 )
                .mirrorY().also().withCavities()

        val holds = holdDown()
            .frontTo( holeR +1 )
            .mirrorY().also().withCavities()
        
        val fins = CircularArc( r + thickness/2, r-2, /*fromAngle*/ 1, /*toAngle*/ 29 )
            .smartExtrude( rimHeight/2 + thickness/2 )
            .outsideBottom( Chamfer(thickness/2) )
            .topTo( rim.top )
            .repeatAroundZ(6)

        val stop = CircularArc( r + thickness/2, r-2, /*fromAngle*/ 1, /*toAngle*/ 4 )
            .smartExtrude(rim.size.z)
            .repeatAroundZ(6)

        return base and rim and screwHolesA and screwHolesB and screwHolesC and holds and fins and stop
    }

    @Slice( brimWidthInterior=5 )
    @Piece( about="A rose that arms can be directly connected to (no central stem)" )
    meth roundedRose() : Shape3d {
        val r = roseDiameter/2 + thickness

        val solid = Circle( r ).smartExtrude( r + rimHeight )
            .top( Fillet( r-1 ) )
            .scaleZ( r / (r + rimHeight) )
    
        val insideHeight = solid.size.z - thickness 
        val inside = Circle( r - thickness )
            .smartExtrude( insideHeight )
                .top( Chamfer(r-thickness-1,insideHeight-rimHeight) )
            .bottomTo(-0.01)

        val holes = rodThread().threadedHole(26)
            .topTo( roseDiameter/2 + 6 )
            .rotateY(armAngle)
            .repeatAroundZ(armCount)
            .color("Orange")

        return solid - inside - holes + roseScrewIn()
    }
    
    @Slice( brimWidthInterior=5 )
    @Piece( about="A rose with a single connection for a centeral stem (arms are not directly connected)" )
    meth stemRose() : Shape3d {
        val outsideR = roseDiameter/2 + thickness
        val insideR = roseDiameter/2

        val main = Circle( outsideR )
            .smartExtrude( stemRoseHeight )
                .top( Fillet( stemRoseFillet ) )

        val neck = Circle( stemSize.x/2 )
            .smartExtrude( 22 )
                .bottom( Fillet( stemRoseFillet ).reverse() )
            .bottomTo( main.top )

        val threadedHole = rodThread().threadedHole(16).topTo( neck.top + 0.01 )
        val wireHole = Cylinder( 4, wireDiameter/2 )
            .topTo( threadedHole.bottom + 1 )

        val hollowOpenRadius = threadedHole.size.x/2 - 6
        val hollow = Circle( insideR )
            .smartExtrude( wireHole.bottom + 1 )
                .top( Chamfer( insideR - hollowOpenRadius, threadedHole.bottom - rimHeight ) )
            .bottomTo(-0.01)

        return main + neck - hollow - wireHole - threadedHole + roseScrewIn()
    }
    
    @Slice( brimWidth=5 )
    @Piece( about="Connects `stemRose` and `stem`" )
    meth stemConnector() : Shape3d {
        val solid = rodThread().threadedRod( 38 )
        val hole = Cylinder( solid.top + 1, wireDiameter/2 )
            .bottomTo( -0.01 )
        return solid - hole
    }

    @Slice( brimWidth=10 )
    @Piece( about="A cylinder from `stemRose to `ballA`" )
    meth stem() : Shape3d {

        val main = Circle( stemSize.x / 2 )
            .smartExtrude( stemSize.y )

        val roseThread = rodThread().threadedHole( 10 )

        val ballEnd = rodThread().threadedRod( 20 )
            .chamferBottom(false)
            .bottomTo( main.top - 10 )

        val wireHole = Circle( wireDiameter/2 )
            .smartExtrude( stemSize.y )
                .bottom( Chamfer( roseThread.size.x/2 - wireDiameter/2 ).reverse() )
            .bottomTo( roseThread.top )

        val ballCurve = Sphere( ballDiameter/2 )
            .bottomTo(main.top-2)

        return main - ballCurve - roseThread + ballEnd - wireHole
    }

    // Piece `arm` has a hollow which the wide part of a shop-bought pendant's domed piece.
    // Piece `arm` is glued to the pendant.
    // This fits the "British General" B22 pendant (with cord grip)
    // https://www.screwfix.com/p/british-general-cordgrip-pendant-lamp-holder-light-fitting-b22/48002
    meth pendantProfile() : Shape2d {
        val width = 33
        val height = 34
        return PolygonBuilder().apply {
            moveTo( 0,0 )
            lineBy( width/2, 0 )
            bezierBy(
                Vector2( -1, 25 ),
                Vector2( -10, 5 ),
                Vector2( -15, height )
            )
            lineTo( 0, height )
        }.build()
    }

    //@Piece( about="To visually compare the shape with the shop-bought pendant")
    //meth testProfile() = pendantProfile().extrude(1).mirrorX().also()

    //@Slice( brimWidthInterior=5 )
    //@Piece( about="To check if the shop-bought pendant fits" )
    //meth testPendant() = arm().intersection( Cylinder(40, 20) ) -
    //    Square( 30, 12 ).centerY().smartExtrude( 30 )
    //        .top(Chamfer(5.9))
    //        .bottomTo(5)

    @Slice( brimWidthInterior=5 )    
    @Piece 
    meth arm() : Shape3d {
        val armRadius = wireDiameter/2 + thickness
        val bud = PolygonBuilder().apply {
            moveTo(0,0)
            lineBy( 19, 0 )
            bezierBy(
                Vector2(5,5),
                Vector2(-10,10),
                Vector2(-8,38)
            )
            bezierBy(
                Vector2(-2,2),
                Vector2(0,4),
                Vector2( 2-armRadius, 10 )
            )
            lineTo( 0, 48 )
        }.build().revolve()

        val hollow = pendantProfile().revolve().bottomTo(-0.01)

        val rod = Circle( armRadius )
            .smartExtrude( armLength )
                .top( roundedChamfer(10).reverse() ) // Original printed used 8
            .bottomTo( bud.top )

        val ballCurve = Sphere( ballDiameter/2 )
            .bottomTo(rod.top-2)

        val rodThread = rodThread().threadedRod( 16 )

        val threadAssembly = if (screwInArms) {
            rodThread.bottomTo( rod.top - 4 )
        } else {
            val locator = armHexShape()
                .smartExtrude(7)
                    .bottom( Chamfer(2, 4) )
                .bottomTo( rod.top -4 )  

            locator + rodThread.bottomTo( locator.top - 1 )    
        }

        val hole = Circle( wireDiameter/2 )
            .smartExtrude( threadAssembly.top + 1 )
    
        return bud + rod  - ballCurve + threadAssembly - hollow - hole
    }
    
    meth armHexShape() = Circle( rodThread().diameter/2 + 1.5 ).sides(6).roundAllCorners(1)

    // Used by ballA and ballB
    meth ballHalf() : Shape3d {

        val main = Sphere( ballDiameter/2 ) - Cube( ballDiameter*2 ).centerXY().topTo(0)
        val mainThread = ballThread().threadedHole( 10 )
            .label("mainThread")

        val hollowRadius = ballThread().diameter/2
        val hollowHeight = main.top - mainThread.top - thickness
        val hollow = Circle( hollowRadius )
            .smartExtrude( hollowHeight )
                .top( Chamfer( hollowRadius-1, hollowHeight-1 ) )
            .bottomTo( mainThread.top - 0.01 )

        return main - mainThread - hollow
    }

    @Slice( brimWidthInterior=5 )
    @Piece
    meth ballA() : Shape3d {
        val half = ballHalf()
        val mainThread = half.find("mainThread")

        val stemHole = rodThread().threadedHole(20)
            .topTo( half.top + 0.01 )
        
        val wireHole = Cylinder( ballDiameter/2, wireDiameter/2 )
            .bottomTo( mainThread.top + 0.4 )

        val hole = if ( screwInArms ) {
            rodThread().threadedHole(22)
        } else {
            armHexShape().offset(0.5).extrude(22)
        }
        
        val holes = if (armCountHigh > 0 ) {
            hole
                .topTo( half.top + 1 )
                .rotateY(armAngleHigh)
                .repeatAroundZ(armCountHigh)
                .color("Orange")

        } else {
            null
        }

        return half - stemHole - wireHole - holes
    }

    @Slice( brimWidthInterior=5 )
    @Piece
    meth ballB() : Shape3d {
        val half = ballHalf()
        
        val hole = if ( screwInArms ) {
            rodThread().threadedHole(22)
        } else {
            armHexShape().offset(0.5).extrude(22)
        }

        val holes = hole
            .topTo( half.top + 1 )
            .rotateY(armAngle)
            .repeatAroundZ(armCount)
            .color("Orange")

        return half - holes
    }

    @Piece( about="Used to attach 'arms' when `armsScrewIn`==false" )
    meth armNut() : Shape3d {
        val nut = rodThread().nut()
        val envelope = Sphere( ballDiameter/2 )
            .topTo( nut.top )

        return nut.intersection( envelope )
    }

    @Slice( brimWidthInterior=5 )
    @Piece
    meth ballConnector() : Shape3d {
        val main = ballThread().threadedRod( 18 )
        val hole = Cylinder(main.top + 2, ballThread().coreRadius() - 2 )
            .bottomTo(-1)
        return main - hole
    }

    @Piece( printable=false )
    meth previewWithStem() : Shape3d {

        val rose = stemRose().mirrorZ()
        val roseFixture = roseFixture().mirrorZ()
            .color("Green")

        val stem = stem().mirrorZ().topTo( rose.bottom + 10 )

        val ballA = ballA()
            .topTo( stem.bottom + 10 )
        val ballB = ballB().mirrorZ().bottomTo(ballA.bottom-3)

        val arm = arm().color("Green")
        
        val arms = arm
            .topTo(-ballDiameter/2+16).rotateY(-armAngle)
            .translateZ(ballB.top-ballDiameter/2)
            .repeatAroundZ(3)

        return rose + roseFixture + stem + ballA + ballB + arms
    }

    @Piece( printable=false )
    meth previewRoundedRose() : Shape3d {

        val arm = arm().color("Green")
        
        val arms = arm
            .topTo( -roseDiameter/2 + 6 )
            .rotateY(-armAngle)
            .repeatAroundZ(3)

        val rose = roundedRose().mirrorZ()

        val roseFixture = roseFixture().mirrorZ()
            .color("Green")

        return arms + rose + roseFixture

    }

    @Piece( printable=false )
    override meth build() = previewWithStem()

}