/Furniture/TrippleLightShade.foocad

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