import uk.co.nickthecoder.foocad.smartextrusion.v1.* import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.* import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import uk.co.nickthecoder.foocad.extras.v1.* import static uk.co.nickthecoder.foocad.extras.v1.Extras.* import static LightShadeFlower.* /** TODO Correct value for fixtureDiameter Slots in petals. Petals do not overlap the ribs by an even amount (now broken - not lining up!). The bulb should be in the middle of the flower. Model a typical light fitting and bulb. Allow the sphere to be squashed */ class LightShadeFlower : Model { // Each line below represents a ring of 12 petals. // The three numbers are : // 1. Position of the ring as an angle in degrees (like lines of latitude) // 2. The tilt of the petal (0 = horizontal, 90 = straight down) // 3. The size of the petal (in bizare units that aren't important - adjust the values by eye!) // The size isn't in millimeters to make it easy to change the overall size of the // shade without changing the data. val petalData = listOf( Petal( -27, -60, 46 ), Petal( -15, -50, 54 ), Petal( 5, -45, 60 ), Petal( 27, -30, 54 ), Petal( 46, -20, 46 ), Petal( 57, -3, 34 ) ) // The actual shade will be large than this. @Custom( about="The diameter of the sphere which forms the core of the lamp" ) var diameter = 225 @Custom var topAngle = -50 @Custom var bottomAngle = 60 @Custom var ribSize = Vector2( 20, 2 ) @Custom var petalThickness = 1.2 @Custom var ribCount = 24 @Custom var fixtureDiameter = 35 @Custom( about="A small chamfer on the structual elements (avoids elephant foot issues)" ) var ribChamfer = 0.5 @Custom( about="Clearances for the wider slots (length,width)" ) var slotClearance = Vector2( 0.2, 0.2 ) // It is likely that these values will be identical to `slotClearance`. @Custom( about="Clearances of the narrow slots into which the petals are placed (length,width)" ) var petalSlotClearance = Vector2( 0.2, 0.2 ) meth extrudeRib( shape : Shape2d ) = shape.smartExtrude( ribSize.y ) .edges( ProfileEdge.chamfer( ribChamfer ) ) meth topPoint() = Vector2( diameter/2, 0 ).rotateDegrees( topAngle ) meth bottomPoint() = Vector2( diameter/2, 0 ).rotateDegrees( bottomAngle ) meth mainSlot() : Shape2d = Square( ribSize.x + slotClearance.x, ribSize.y + slotClearance.y) .centerY() .margin( -slotClearance.x, 0 ) meth petalSlot() : Shape2d = Square( ribSize.x + petalSlotClearance.x, petalThickness + petalSlotClearance.y) .centerY() .margin( -petalSlotClearance.x, 0 ) // The pieces should slot together easily, with a slight friction fit. // If the fit is too tight increase slotClearance/petalSlotClear Y value. // When pushed all the way in, the edges should align. // If they don't, adjust slotClearance/petalSlotClear X value. // Use the same print settings and filament that you will use for the actual pieces. @Piece( about="Test pieces to help adjust slotClearance and petalClearance" ) meth testFitRib() : Shape3d { return ( Square( ribSize.x ).centerY() - mainSlot().rightTo(ribSize.x/2) - petalSlot().centerYTo( ribSize.x * 0.25 ).leftTo(ribSize.x/2) ).extrude( ribSize.y ).tileX(2, 1) } @Piece meth testFitPetal() : Shape3d { return extrudeRib( Square( ribSize.x ).centerY().roundAllCorners(2) - mainSlot().rightTo(ribSize.x/2) ) } @Piece meth lowerTop() : Shape3d { val topPoint = topPoint() val shape = Circle( topPoint.x + ribSize.x/2 ) - Circle(topPoint.x - ribSize.x/2 ) val slots = mainSlot() .leftTo( topPoint.x ) .repeatAround( ribCount ) return extrudeRib(shape - slots) } @Piece meth top() : Shape3d { val outer = lowerTop() val topPoint = topPoint() val inner = extrudeRib( Circle( fixtureDiameter/2 + ribSize.x ) - Circle( fixtureDiameter/2 ) ) val ribs = Square( topPoint.x - fixtureDiameter + ribSize.x*0.75, ribSize.x/2 - 0.2 ) .roundCorner(3,10) .roundCorner(2,10) .mirrorX() .leftTo( fixtureDiameter/2 + ribSize.x/2 ) .extrude( ribSize.y ) .rotateX(90) .centerY() .rotateZ(360/2/ribCount) .repeatAroundZ( 6 ) return outer + inner + ribs } @Piece meth bottom() : Shape3d { val bottomPoint = bottomPoint() val shape = Circle( bottomPoint.x + ribSize.x/2 ) - Circle(bottomPoint.x - ribSize.x/2 ) return shape.extrude( ribSize.y ) } @Piece meth ribEven() = rib( true ) @Piece meth ribOdd() = rib( false ) @Piece meth ribEven6() = ribEven().rightTo(ribSize.x + 5) .repeatX(3, ribSize.x*2 ) .rotateZ(180).also() @Piece meth ribOdd6() = ribOdd().rightTo(ribSize.x + 5) .repeatX(3, ribSize.x*2 ) .rotateZ(180).also() meth rib( even : bool ) : Shape3d { val topPoint = topPoint() val bottomPoint = bottomPoint() val shape = PolygonBuilder().apply { moveTo( topPoint + Vector2( 0, ribSize.x ) ) lineTo( topPoint ) circularArcTo( bottomPoint, diameter/2, false, false ) }.buildPath().thickness( ribSize.x ) val slot = mainSlot() val petalSlot = petalSlot() var slots : Shape2d = slot .rightTo( topPoint.x ) .centerYTo( topPoint.y + ribSize.x/4 ) .translateY( ribSize.x/2 ).also() var isEven = false for ( petal in petalData ) { isEven = ! isEven if (isEven != even) continue slots += petalSlot //.leftTo( ribSize.x/2 ) .rotate( - petal.tilt + petal.around ) .translateX( diameter/2 + ribSize.x/4 ) .rotate( -petal.around ) } return extrudeRib(shape - slots) } meth petalShape( nominalSize : double ) : Shape2d { val size = diameter/2 * nominalSize/100 val foo = size*0.1 val bar = size*0.4 val baz = size*0.4 val x = size * 1.3 return PolygonBuilder().apply { moveTo( size/2, -size/2 ) circularArcTo( size/2, size/2, size/2, true, false ) bezierByTo( Vector2( baz, 0 ), Vector2( bar, -foo ), Vector2( x, 0 ) ) bezierByTo( Vector2( -bar, -foo ), Vector2( -baz, 0 ), Vector2( size/2, -size/2 ) ) }.build().leftTo( -ribSize.x/2 ) } @Piece @Slice( topFillPattern="concentric", bottomFillPattern="concentric" ) meth petal0() = petal( petalData[0].size ).rotateZ(90) @Piece @Slice( topFillPattern="concentric", bottomFillPattern="concentric" ) meth petal1() = petal( petalData[1].size ).rotateZ(90) @Piece @Slice( topFillPattern="concentric", bottomFillPattern="concentric" ) meth petal2() = petal( petalData[2].size ) @Piece @Slice( topFillPattern="concentric", bottomFillPattern="concentric" ) meth petal3() = petal( petalData[3].size ) @Piece @Slice( topFillPattern="concentric", bottomFillPattern="concentric" ) meth petal4() = petal( petalData[4].size ) meth petal( size : double ) = petalShape( size ).extrude( petalThickness ) @Piece meth middle() : Shape3d { val bottomPoint = bottomPoint() return Circle( bottomPoint.x ).extrude( petalThickness ) } @Piece( printable = false ) meth assembled() : Shape3d { val topPoint = topPoint() val bottomPoint = bottomPoint() val ribsEven = ribEven() .centerZ() .rotateX(90) .repeatAroundZ( ribCount / 2 ) val ribsOdd = ribOdd() .centerZ() .rotateX(90) .rotateZ( 360 / ribCount ) .repeatAroundZ( ribCount / 2 ) val ribs = ribsOdd + ribsEven val top = top() .mirrorZ() .topTo( topPoint.y + ribSize.x * 0.75 + ribSize.y/2 ) val lowerTop = lowerTop() .bottomTo( top.top - ribSize.x/2 - ribSize.y ) val bottom = bottom() .bottomTo( bottomPoint.y + ribSize.x/2 ) val middle = middle() .bottomTo( bottom.top + 0.1 ) .previewOnly() var petals : Shape3d = Union3d() var extraRotation = 0 var maxX = 0 for ( petal in petalData ) { val newPetal = petal( petal.size ) .centerZ() .leftTo( 0 ) // Tilt the petal .rotateY( petal.tilt - petal.around ) // Move to the equator .translateX( diameter/2 ) //+ ribSize.x/2 ) // Move to the correct latitude .rotateY( petal.around ) maxX = Math.max( maxX, newPetal.right ) petals += newPetal // Make copies all the way round // Comment out this line to see only 1 petal of each size. .repeatAroundZ( ribCount/2 ) // alternate between odd and even ribs .rotateZ( extraRotation ) .color( "GhostWhite") // alternate between odd and even ribs extraRotation = 360/ribCount - extraRotation } val diameterText = Text("Total Diameter = ${maxX*2}mm") .extrude(1) .rotateY(-90).rotateZ(90) .topTo(topPoint.y + ribSize.x).leftTo( maxX ) .previewOnly() val holeDiameter = (middle.size.x - ribSize.x).toInt() val holeDiameterText = Text( "Hole = ${holeDiameter}mm" ) .extrude(1) .mirrorX().rotateZ(180).centerY() .topTo( middle.bottom ) .centerX()//.leftTo( -holeDiameter/2 ) .previewOnly() val shade = ribs + top + lowerTop + middle + bottom + petals return shade + diameterText + holeDiameterText } // Rotate the result so that the preview image gives a good impression. // Without the rotation, the image is as if your eyes were near the ceiling! @Piece( printable = false ) override fun build() = assembled().rotateX(-130).rotateZ(15) } class Petal( val around : double, val tilt : double, val size : double ) { }