Exit Full View
ZipUp

/Furniture/LightShadePineCone.foocad

LightShadePineCone

Inspired by : https://www.youtube.com/watch?v=zluFU6wmua4

Unfinished. See script for details.

I liked the look of this lamp shape, but I wanted it much smaller. It's not the kind of model that can be easily scaled down, so I redesigned it from scratch. However, after I spent an hour or so creating this, I realised I didn't like it! It only looks good when the at eye level (or below). I looks naff when seen from below. YMMV.

This version is highly parametric, although the position, sizes and angles of the "petals" cannot be changed from the "Customiser" tab, but should be easy to do by changing the data near the top of the script.

FooCAD Source Code
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.*

/**
TODO.
    Cut slots in the petals and the ribs.
    Cut slots for the ribs to join with the top/bottom pieces.
    Add a connection for the light.
    Tinker with the petalData.
*/
class LightShadePineCone : Model {

    // Each line below represents 12 "petals". The three numbers are :
    // 1. Position of the petal 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 - tweak values by eye!)
    //    The size isn't in millimeters to make it easy to change the overall size of the
    //    light shade without changing the data.
    
    val petalData = listOf<Petal>(
        Petal( -58, 10, 30 ),
        Petal( -50, 16, 36 ),
        Petal( -40, 20, 46 ),
        Petal( -30, 30, 56 ),
        Petal( -15, 40, 60 ),
        Petal(   0, 45, 60 ),
        Petal(  15, 55, 60 ),
        Petal(  30, 65, 50 ),
        Petal(  40, 80, 40 )
    )
    

    @Custom( about="The diameter of the sphere which forms the core of the lamp" )
    var diameter = 200

    @Custom
    var topAngle = -65

    @Custom
    var bottomAngle = 60

    @Custom
    var ribSize = Vector2( 20, 2 )

    @Custom
    var petalThickness = 1.2

    @Custom
    var ribCount = 24

    meth topPoint() = Vector2( diameter/2, 0 ).rotateDegrees( topAngle )
    meth bottomPoint() = Vector2( diameter/2, 0 ).rotateDegrees( bottomAngle )

    @Piece
    meth top() : Shape3d {

        val topPoint = topPoint()
        val shape = Circle( topPoint.x ) - Circle(topPoint.x - ribSize.x )

        return shape.extrude( ribSize.y )
    }

    @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 rib() : 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 )

        return shape.extrude( ribSize.y )
    }

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

    @Piece
    meth petal() = petal( petalData[0].size )

    meth petal( size : double ) = petalShape( size ).extrude( petalThickness )

    @Piece( printable=false )
    override fun build() : Shape3d {
        val topPoint = topPoint()
        val bottomPoint = bottomPoint()

        val ribs = rib()
            .centerZ()
            .rotateX(90)
            .repeatAroundZ( 24 )

        val top = top()
            .topTo( topPoint.y + ribSize.x*0.75 )
            .translateZ( -ribSize.x/2 ).also()

        val bottom = bottom()
            .bottomTo( bottomPoint.y + ribSize.x/2 )

        var petals : Shape3d = Union3d()
        var extraRotation = 0

        for ( petal in petalData ) {
            petals += petal( petal.size )
                // 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 )
                .translateX( - ribSize.x/2 )
                // 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
        }

        return ribs + top + bottom + petals
    }

}

class Petal(
    val around : double,
    val tilt : double,
    val size : double
) {
}