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() }