Exit Full View
Up

/Boxes/SmoothOutsideBox.foocad

SmoothOutsideBox

A box, with thin walls, but thicker there the lip of the lid meets the main body.

The box can be built in 2, 3 or more parts. You will always need 1 "top" and 1 "bottom", and you can add 0 or more "middle" pieces.

"slack" is quite and important @Custom parameter, and will need fine tuning for your partiuclar printer. Too small, and the lid will jam on too tightly. Too large, and the lid will be horribly loose. Only the "top" piece uses "slack", so if you need to adjust it, only the "top" piece needs to be re-printed.

The shape of the box is arbitrary (override profile() method).

To get all the other profile sizes (for the lip, inside, chamfer etc), I currently use "scale". This ensures it works with any profile you want, but has the disadvantage that it doesn't produce perfectly consistant width lips.

The "better" solution would be to use Shape2d.offset(n), but this can produce non 1-to-1 correspondance between the points that make up sucessive profiles, and ExtrusionBuilder does a very bad job sometimes! A future version of FooCAD may fix this, and then Shape2d.offset will be better than Shape2d.scale.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedExtrude.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.layout.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.Layout3d.*

class SmoothOutsideBox : Model {
    
    @Custom
    var height = 40

    @Custom
    var topHeight = 10

    @Custom
    var middleHeight = 30

    @Custom
    var topChamfer = 2.0

    @Custom
    var bottomChamfer = 2.0

    @Custom
    var lipHeight = 8.0

    @Custom
    var lipThickness = 2.0

    @Custom
    var wallThickness = 1.0

    @Custom
    var baseThickness = 2.0

    @Custom
    var slack = 0.3


    fun profile() : Shape2d = Square( 70 ).center()
    
    fun inset( amount : double ) : Shape2d {
        val profile : Shape2d = profile()
        return profile.scale(
            (profile.size.x - amount * 2) / profile.size.x,
            (profile.size.y - amount * 2) / profile.size.y
        )
    }

    @Piece
    fun top() : Shape3d {
        val profile : Shape2d = profile()
        val chamfered = inset( topChamfer )
        val lipA = inset( slack + lipThickness )       
        val lipB = inset( slack + lipThickness*2 )
        val inside = inset( wallThickness )
        val insideChamfer = inset( wallThickness + topChamfer )

        return ExtrusionBuilder().apply {
            joinStrategy = OneToOneJoinStrategy.instance

            crossSection( chamfered )
            forward( topChamfer )
            crossSection( profile )
            forward( topHeight - topChamfer )
            crossSection()
            crossSection( lipA )
            forward( lipHeight )
            crossSection()
            crossSection( lipB )
            forward( -lipHeight - lipThickness/2 )
            crossSection()

            val foo = - topHeight + 
                lipThickness*2 + slack - wallThickness +
                lipThickness/2 +
                baseThickness + topChamfer
            if (foo < 0) {

                forward( - lipThickness*2 - slack + wallThickness )
                crossSection(inside)
                forward( foo )
            }

            crossSection()
            if ( foo < 0 ) {
                forward( -topChamfer )
            } else {
                //forward( foo - topChamfer )
            }
            crossSection( insideChamfer )
        }.build().mirrorX()
    }

    @Piece
    fun middle() : Shape3d {
        if ( middleHeight <= 0 ) return Cube(0)

        val profile : Shape2d = profile()
        val bottomLipA = inset( slack + lipThickness  )
        val bottomLipB = inset( slack + lipThickness*2 )
        val inside = inset( wallThickness )
        val topLipA = inset( lipThickness )

        return ExtrusionBuilder().apply {
            joinStrategy = OneToOneJoinStrategy.instance

            crossSection( profile )
            forward( middleHeight )
            crossSection()
            crossSection( bottomLipA )
            forward( lipHeight )
            crossSection()
            crossSection( bottomLipB )
            forward( -lipHeight - lipThickness/2 )
            crossSection()
            forward( -lipThickness * 2 - slack + wallThickness )
            crossSection( inside )
            forward( - middleHeight +
                lipThickness*2 + slack - wallThickness +
                lipHeight + lipThickness/2 - lipHeight +
                lipHeight + lipThickness - wallThickness // Matches below
            )
            crossSection()
            forward( -lipThickness + wallThickness )
            crossSection( wallThickness - lipThickness )
            forward( -lipHeight )
            crossSection()

        }.buildClosed().mirrorX() 
    
    }

    @Piece
    fun bottom() : Shape3d {
        val profile : Shape2d = profile()
        val chamfered = inset( bottomChamfer )
        val lipA = inset( lipThickness )
        val inside = inset( wallThickness )
        val insideChamfer = inset( wallThickness + bottomChamfer )

        return ExtrusionBuilder().apply {
            joinStrategy = OneToOneJoinStrategy.instance

            crossSection( chamfered )
            forward( bottomChamfer )
            crossSection( profile )
            forward( height - bottomChamfer )
            crossSection()

            crossSection( lipA )
            forward( -lipHeight )
            crossSection()
            forward( -lipThickness + wallThickness )
            crossSection( inside )
            forward( -height - wallThickness + lipThickness + lipHeight + bottomChamfer + baseThickness )
            crossSection()
            forward( -bottomChamfer )
            crossSection( insideChamfer )
        }.build()
    }

    @Piece

    override fun build() : Shape3d {
        
        val gap = 10

        return bottom()
                .centerXY().color("Blue") +
            middle()
                .rotateX(180)
                .translateZ(height + middleHeight + slack + gap)
                .centerXY().color("Silver") +
            top()
                .rotateX(180).rotateZ(180)
                .translateZ(height + middleHeight + topHeight + slack*2 + gap*2 ) 
                .centerXY().color("Yellow")
    }
}