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.* 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( -38, -60, 45 ), Petal( -20, -50, 57 ), Petal( 5, -45, 62 ), Petal( 27, -30, 55 ), Petal( 40, -12, 45 ), Petal( 50, -2, 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( about="Flatten the sphere by making this less than 1" ) var scaleY = 0.75 // I believe this is safe to change without adjusting petalData or other attributes. @Custom( about="Stretch the petals outwards" ) var petalScaleX = 1.0 @Custom( about="How far is the tip of the petal compared to its radius?" ) var petalTip = 1.4 @Custom var topAngle = -60 @Custom var bottomAngle = 60 @Custom var ribSize = Vector2( 16, 2 ) @Custom var petalThickness = 0.8 @Custom var ribCount = 24 @Custom var fixtureDiameter = 29 @Custom( about="A small chamfer on the structual elements (avoids elephant foot issues)" ) var ribChamfer = 0.5 @Custom( about="A small chamfer on the petals elements (avoids elephant foot issues)" ) var petalChamfer = 0.2 @Custom( about="Clearances for the wider slots (length,width)" ) var slotClearance = Vector2( 0, 0.025 ) // 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.0, 0.0 ) meth extrudeRib( shape : Shape2d ) = shape.smartExtrude( ribSize.y ) .edges( ProfileEdge.chamfer( ribChamfer ) ) meth circularPointForAngle( diameter : double, angle : double ) : Vector2 { return Vector2( diameter/2 * cos( angle ), -diameter/2 * sin( angle ) ) } meth circularMidPointForAngle( angle : double ) = circularPointForAngle( diameter, angle ) meth squashedMidPointForAngle( angle : double ) = circularPointForAngle( diameter, angle ) * Vector2(1, scaleY) meth squashedOutsidePointForAngle( angle : double ) = squashedMidPointForAngle( angle ) + Vector2( ribSize.x/2 * cos( angle ), -ribSize.x/2 * sin( angle ) ) meth mainSlot() : Shape2d { val main = Square( ribSize.x + slotClearance.x, ribSize.y + slotClearance.y) .center() // Adds a tiny chamfer to the corners, just in case the printer oozes too much // filament when turning the 90° corner. val bevel = Square( ribSize.y + 0.5 ).center().rotate(45) return (main + bevel) .margin( -slotClearance.x, -slotClearance.y ) } meth petalSlot() : Shape2d { val main = Square( ribSize.x + petalSlotClearance.x, petalThickness + petalSlotClearance.y) .center() // The same trick used by mainSlot doesn't work here, because the slots aren't at right // angles. //val bevel = Circle(petalThickness+0.4) return (main) .margin( -petalSlotClearance.x, -petalSlotClearance.y ) } // 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 extrudeRib( Square( ribSize.x, ribSize.x*2 ).centerY() - mainSlot().rightTo(ribSize.x/2) - petalSlot().centerYTo( ribSize.x * 0.5 ).leftTo(ribSize.x/2) - petalSlot().rotate(-30).centerYTo( -ribSize.x * 0.9 ).leftTo(ribSize.x/2) ).tileX(2, 1) } @Piece meth testFitPetal() : Shape3d { return ( Square( ribSize.x*1.5 ).centerY().roundAllCorners(2) - mainSlot().rightTo(ribSize.x/2) ).smartExtrude( petalThickness ) .edges( ProfileEdge.chamfer( petalChamfer ) ) } @Piece meth lowerTop() : Shape3d { val topPoint = squashedMidPointForAngle( topAngle ) 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 = squashedMidPointForAngle( topAngle ) val inner = extrudeRib( Circle( fixtureDiameter/2 + ribSize.x ) - Circle( fixtureDiameter/2 ) ) val ribShape = Square( topPoint.x - fixtureDiameter + ribSize.x*0.75, ribSize.x/2 ) .roundCorner(3,10) .roundCorner(2,10) .mirrorX() .leftTo( fixtureDiameter/2 + ribSize.x/2 ) val ribs = extrudeRib( ribShape ) .rotateX(90) .centerY() .rotateZ(360/2/ribCount) .repeatAroundZ( 6 ) return outer + inner + ribs } @Piece meth bottom() : Shape3d { val bottomPoint = squashedMidPointForAngle( bottomAngle ) val shape = Circle( bottomPoint.x + ribSize.x/2 ) - Circle(bottomPoint.x - ribSize.x/2 ) val slots = mainSlot() .leftTo( bottomPoint.x ) .repeatAround( ribCount ) return extrudeRib(shape - slots) } @Piece meth ribEven() = rib( true ) @Piece meth ribOdd() = rib( false ) meth rib( even : bool ) : Shape3d { val circularTopPoint = circularMidPointForAngle(topAngle) val circularBottomPoint = circularMidPointForAngle(bottomAngle) val squashedTopPoint = squashedMidPointForAngle(topAngle) val squashedBottomPoint = squashedMidPointForAngle(bottomAngle) val shape = PolygonBuilder().apply { moveTo( circularTopPoint + Vector2( 0, ribSize.x*1.125 / scaleY ) ) lineTo( circularTopPoint ) circularArcTo( circularBottomPoint, diameter/2, false, false ) lineBy( -ribSize.x*0.5, 0 ) }.buildPath().scaleY( scaleY ).thickness( ribSize.x ) val slot = mainSlot() val petalSlot = petalSlot() var slots : Shape2d = slot .rightTo( squashedTopPoint.x ) .centerYTo( squashedTopPoint.y + ribSize.x/8 ) .translateY( ribSize.x/2 ).also() slots += slot .rightTo( squashedBottomPoint.x ) .centerYTo( squashedBottomPoint.y ) var isEven = false for ( petal in petalData ) { isEven = ! isEven if (isEven != even) continue slots += petalSlot //.translateX( ribSize.x/2 ) // TODO ??? .rotate( -petal.tilt ) .translate( squashedOutsidePointForAngle( petal.around ) ) } // return extrudeRib(shape - slots.offset(1) + slots) // Shows slots clearly 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 tip = petalTip * size 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( tip, 0 ) ) bezierByTo( Vector2( -bar, -foo ), Vector2( -baz, 0 ), Vector2( size/2, -size/2 ) ) }.build().leftTo( -ribSize.x/2 ).scaleX( petalScaleX ) } meth petal( size : double ) : Shape3d { val shape = petalShape( size ).leftTo(0) val slot = mainSlot().smartExtrude( petalThickness ) .bottom( ProfileEdge.chamfer( -petalChamfer ) ) return shape.smartExtrude( petalThickness ) .edges( ProfileEdge.chamfer( petalChamfer ) ) - slot } meth petalForTiling( size : double ) : Shape3d { val shape = petalShape( size ).leftTo(0).rotate(45) val slot = mainSlot().rotate(45).smartExtrude( petalThickness ) .bottom( ProfileEdge.chamfer( -petalChamfer ) ) return shape.smartExtrude( petalThickness ) .edges( ProfileEdge.chamfer( petalChamfer ) ) - slot } @Piece // hilbertcurve concentric //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal0() = petalForTiling( petalData[0].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal1() = petalForTiling( petalData[1].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal2() = petalForTiling( petalData[2].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal3() = petalForTiling( petalData[3].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal4() = petalForTiling( petalData[4].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal5() = petalForTiling( petalData[5].size ) @Piece //@Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) @Slice( onlyOnePerimeterTop=0, topFillPattern="hilbertcurve" ) meth petal6() = petalForTiling( petalData[6].size ) @Piece @Slice( onlyOnePerimeterTop=0, bottomFillPattern="archimedeanchords", topFillPattern="archimedeanchords" ) meth bottomDisk() : Shape3d { val bottomPoint = squashedMidPointForAngle( bottomAngle ) val circle = Circle( bottomPoint.x - ribSize.x/4 ) val slots = mainSlot().offset(0.5).centerXTo( bottomPoint.x ) .repeatAround( ribCount ) val shape = circle - slots return shape.extrude( 0.4 ) } @Piece( printable = false ) meth assembled() : Shape3d { val topPoint = squashedMidPointForAngle( topAngle ) val bottomPoint = squashedMidPointForAngle( bottomAngle ) 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 * 5/8 + ribSize.y/2 ) val lowerTop = lowerTop() .bottomTo( top.top - ribSize.x/2 - ribSize.y ) val bottom = bottom() .centerZTo( bottomPoint.y ) val bottomDisk = bottomDisk() .bottomTo( bottom.top + 0.1 ) .color("Yellow").opacity(0.4) var petals : Shape3d = Union3d() var extraRotation = 0 var maxX = 0 for ( petal in petalData ) { val point = squashedOutsidePointForAngle( petal.around ) val newPetal = petal( petal.size ) .centerZ() .leftTo( -ribSize.x ) // Tilt the petal .rotateY( petal.tilt ) // Move it to the correct position based on PetalData.around. .translate( point.x, 0, point.y ) 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 = (bottomDisk.size.x - ribSize.x).toInt() val holeDiameterText = Text( "Hole = ${holeDiameter}mm" ) .extrude(1) .mirrorX().rotateZ(180).centerY() .topTo( bottomDisk.bottom ) .centerX() .previewOnly() val structure = (ribs + top + lowerTop + bottom) .color( QF_PLA.hickoryBrown ) val scale = 85/44.0 val cap = Circle( 14/2 * scale ) .smartExtrude( 25 *scale ) .top( Fillet( 5 * scale ) ) .centerZTo( top.top ) val flex = Cylinder( 55*scale, 4 ) .bottomTo( cap.top ) val rose = Cylinder( 20, 85/2 ) .bottomTo( flex.top ) val bulb = Sphere(60/2) .bottomTo( cap.middle.z - 108 ) val electrical = (cap + flex + rose + bulb).previewOnly() return structure + petals + diameterText + holeDiameterText + electrical + bottomDisk } @Piece( printable = false ) override fun build() = assembled().rotateY(180) } class Petal( val around : double, val tilt : double, val size : double ) { }