Exit Full View
ZipUp

/Games/PokerChipHolder.foocad

PokerChipHolder

A container for poker chips. The set I own came in a very long case, which is annoying to store, and has more chips than I ever use. It's also overly heavy to take to a friend's house.

Print a "tube" and a "cap" to hold a small number of chips. The cap screws into the tube (so the chips cannot spill out). Alas, this cap can easily be ripped out, rather than unscrewed when the tube is long. Less of a problem when using a stiff filament.

Combine 6 of these with "caddy", "lock" and "handle". The alternative "smallLock" only locks the tubes in place, but allows the caps and chips to be removed. I prefer the larger lock, because I hand each player an entire tube of chips, so I never take individual chips from the caddy.

"cap" has an alternative with "dealer" written on it to act as a dealer button.

Caddies can be loosely stacked (there is a hole in the base which accomodates the handle) Caddies can also be screwed together, with only the top-most caddy needing a "lock" and "handle".

The default "chipsPerColumn" is 30, therefore the default caddy holds 180 chips.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.arrange.v1.Arrange.*
import static uk.co.nickthecoder.foocad.circular.v1.Circular.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import uk.co.nickthecoder.foocad.threaded.v2.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*

class PokerChipHolder : Model {

    @Custom( about="Diameter of the tubes (add extra for clearance)" )
    var diameter = 41.0

    @Custom
    var chipHeight = 172.0/50

    @Custom
    var chipsPerColumn = 30

    @Custom( about="If you want TWO tubes per column within the caddy." )
    var extraChipsPerColumn = 0

    @Custom
    var thickness = 3

    @Custom
    var baseThickness = 6

    @Custom
    var accessAngle = 60

    @Custom
    var displayChipCount = true

    @Custom
    var threadLength = 6

    @Custom
    var clearance = 0.3

    @Custom
    var capText = "Dealer"

    var lockHeight = 12

    meth thread() : Thread {
        return Thread( diameter + 1.5, 2 )
            .rodChamfer( 1.0 )
    }

    meth handleThread() = Thread( 16, 3 )

    meth lockThread() = Thread( diameter - thickness, 3 ).rodChamfer( 1 )

    meth packOffset() = diameter+thickness+2 + 2

    meth dovetail() : Shape2d {
        return Trapezium( diameter*0.4, 6 ).angle(30)
            .mirrorY().centerX().frontTo( -diameter/2 - thickness-3 )
            .roundAllCorners(2)
    }

    meth tube( chipCount : int ) : Shape3d {

        val length = chipHeight * chipCount

        val base = (Circle( diameter/2 + thickness ) + dovetail() )
            .smartExtrude( thickness )
            .bottom( Chamfer( thickness/3 ) )

        val ring = RoundedCircularArc( diameter/2 + thickness, diameter/2, 90+accessAngle/2, 90-accessAngle/2 )
        val tubeShape = dovetail() - Circle( diameter/2 ) + ring
        val tube = tubeShape.extrude( length + threadLength ).bottomTo( base.top )

        val thread = thread().threadedHole( threadLength )
            .chamferStart(false)
            //.chamferEnd(false)
            .topTo( tube.top )

        val count = if (displayChipCount) {
            Text( "$chipCount" ).center().mirrorX()
                .extrude(0.2)
                .bottomTo( -0.01 )
        } else {
            null
        }

        return base + tube - thread - count
    }

    @Piece
    @Slice( topSolidLayers=6 )
    meth tube() = tube( chipsPerColumn )

    @Piece
    @Slice( topSolidLayers=6 )
    meth tubeExtra() = tube( extraChipsPerColumn )

    @Piece
    @Slice( topSolidLayers=6 )
    meth cap() : Shape3d {

        val base = Circle( diameter/2 + thickness )
            .smartExtrude( thickness )
            .bottom( Chamfer( thickness/3 ) )

        val thread = thread().threadedRod( threadLength + thickness - 0.5 )

        return base + thread
    }

    meth labelledCap( label : String ) : Shape3d {
        val fontSize = if (label.size() <3) 18 else 9
        return labelledCap( label, fontSize )
    }

    meth labelledCap( label : String, fontSize : double ) : Shape3d {
        val plain = cap()
        val delear = Text(label, fontSize)
            .hAlign(HAlignment.CENTER)
            .lineSpacing(0.9)
            .toPolygon().center().extrude(0.6)
            .topTo( plain.top + 0.01 )

        return plain - delear
    }

    @Piece
    @Slice( topSolidLayers=6 )
    meth dealerCap() = labelledCap( capText )

    @Piece
    @Slice( topSolidLayers=6 )
    meth handTypeCaps() : Shape3d {
        val fontSize = 8
        return arrangeY(
            arrangeX(
                labelledCap("Pair", fontSize),
                labelledCap("Two\nPair", fontSize),
                labelledCap("Trips", fontSize),
                labelledCap("Straight", fontSize)
            ).centerX(),
            arrangeX(
                labelledCap("Flush", fontSize),
                labelledCap("Full\nHouse", fontSize),
                labelledCap("Quads", fontSize),
                labelledCap("Str8\nFlush", fontSize)
            ).centerX()
        )
    }
    
    @Piece
    meth caddy() : Shape3d {
        var coreHeight = chipsPerColumn * chipHeight + thickness + threadLength
        if ( extraChipsPerColumn > 0 ) {
            coreHeight += extraChipsPerColumn * chipHeight + thickness*2 + threadLength
        }

        val baseShape = Circle( diameter *1.75 + thickness + 3 ).sides(6).rotate(30)
            .roundAllCorners(diameter/2)
        val base = baseShape
            .smartExtrude( baseThickness )
                .bottom( Chamfer(0.6) )
                .top( Chamfer(1) )
        val baseIndents = Circle( diameter/2 + thickness - 0.5 )
            .translateY( packOffset() )
            .smartExtrude( 1 )
            .bottom( Chamfer(0.5) )
            .topTo( base.top + 0.01 )
            .repeatAroundZ(6)

        val outside = Circle( diameter/2 + thickness + 9 )
            .sides(6).roundAllCorners(3)
        val remove = (Circle( diameter/2 + thickness ) + dovetail())
            .offset(clearance).translateY( packOffset() )
            .repeatAround(6)

        val core = ( outside - remove ).extrude( coreHeight )
            .bottomTo( base.top )

        val forLock = lockThread().threadedRod( lockHeight )
            .chamferBottom(false)
            .bottomTo( core.top )

        val holeForHandle = handleThread().threadedHole(20).topTo( forLock.top+0.01 )

        val bottomThread = lockThread().threadedHole( lockHeight + 1 )
            .chamferEnd(false)
        val bottomCone = Cylinder( diameter, lockThread().diameter/2, 1 )
            .bottomTo( bottomThread.top )

        // forces extra perimeters to give move strength - help prevent the core snapping along layer lines.
        val reinforce = Square(0.6).center()
            .translateX( diameter/2 + 1 ).translateX(7).also()
            .extrude(core.top*0.7).bottomTo(0.4)
            .repeatAroundZ(6)
            .color("Red")
    
        return base - baseIndents + core + forLock - holeForHandle - reinforce - bottomThread - bottomCone
    }

    @Piece( about="A mini-caddy for just 2 tubes." )
    meth braceCaddy() : Shape3d {
        var coreHeight = chipsPerColumn * chipHeight + thickness + threadLength

        val packOffset = diameter/2 + 9

        val indentsShape = Circle( diameter/2 + thickness - 0.5 )
            .translateY( packOffset )
            .mirrorY().also()

        val postShape = Square( diameter - 10, 16 ).center()
            .roundAllCorners(3)
        val baseShape = indentsShape.offset(3) + postShape
        val base = baseShape
            .smartExtrude( baseThickness )
                .bottom( Chamfer(0.6) )
                .top( Chamfer(1) )

        val indents = indentsShape
            .smartExtrude( 1 )
            .bottom( Chamfer(0.5) )
            .topTo( base.top + 0.01 )
            .repeatAroundZ(2)

        val post = postShape
            .smartExtrude( coreHeight + base.top )
            .edges( Chamfer(0.6) )
        
        val handle = Square( post.size.x, 26 ).center()
            .roundCorners(listOf<int>(2,3),12.5)
            .smartExtrude(3)
                .edges( Fillet(1) )
            .rotateX(90).center()
            .bottomTo( post.top - 3 )
        val handleGrip = Circle(8)
            .smartExtrude(1)
            .top( ProfileEdge.cove(1).reverse() )
            .rotateX(90).center()
            .frontTo( handle.front - 0.01 )
            .topTo( handle.top -4 )
            .mirrorY().also()


        val grooves = (Circle( diameter/2 + thickness ) + dovetail())
            .offset(clearance).translateY( packOffset )
            .extrude( post.top )
            .bottomTo( indents.bottom )
            .mirrorY().also()

        val remove = (Circle( diameter/2 + thickness ) + dovetail())
            .offset(clearance)
            .translateY( packOffset )
            .repeatAround(6)

        val tube = tube().bottomTo( base.top ).translateY(packOffset).previewOnly()

        return (base - indents) + (post - grooves) + tube + (handle - handleGrip)
    }

    @Piece( about="Tests the fit of the interlocking parts (core and tube)" )
    meth testCaddy() : Shape3d {
        val plain = caddy().translateZ( -baseThickness + 1.8 )
        val clip = Cube( diameter + thickness*2, diameter*2, plain.top ).centerX()
            .frontTo( diameter/2-1 )

        return plain.intersection( clip )
    }

    @Piece
    meth handle() : Shape3d {
        val handleWidth = diameter-10 // Must be small enough to fit inside piece "lock"
        val slice = 2.5
        val thread = handleThread().threadedRod(20)
            .rotateX(90).bottomTo(-slice)
        val slicedThread = thread - Cube( 50 ).centerXY().topTo(0)

        val outside = Square( handleWidth ).centerX()
            .roundCorners( listOf<int>(2,3), handleWidth/2-0.1 )
            .roundCorners( listOf<int>(0,1), 6 )
            .frontTo(-3)
        val handle = (outside - outside.offset(-5) )
            .smartExtrude( slicedThread.top + 1 )
            .edges( ProfileEdge.roundedChamfer(3) )

        return handle + slicedThread
    }

    @Piece
    meth smallLock() : Shape3d {
        val solid = Circle( packOffset()/2 - 0.5 )
            .smartExtrude( lockHeight )
            .bottom(Chamfer(1))

        val thread = lockThread().threadedHole( lockHeight + clearance )
            .topTo( solid.top + 0.01 )

        return solid - thread
    }

    @Piece
    meth lock() : Shape3d {
        val extra = 8
        val  solid = Circle( packOffset()/2 - 0.5 + extra )
            .smartExtrude( lockHeight )
            .bottom( ProfileEdge.chamfer(1.5) )
            .top( ProfileEdge.step(extra, thickness - clearance) )

        val thread = lockThread().threadedHole( lockHeight + clearance )
            .topTo( solid.top + 0.01 )

        return solid - thread
    }

    override meth build() : Shape3d {
        val singleTube = tube().color("MediumPurple")
        val tube = if (extraChipsPerColumn == 0) {
            singleTube
        } else {
            singleTube + tubeExtra().bottomTo( singleTube.top + thickness ).color("MediumPurple").darker()
        }

        val cap = cap().rotateX(180).topTo( tube.top + thickness )
            .color("Orange")
        val single = (tube + cap).translateY( packOffset()  )
        val six = single.repeatAroundZ(3)

        val caddy = caddy().translateZ(-baseThickness).color("Green")
        //val lock = smallLock().rotateX(180).bottomTo( tube.top ).color( "DarkSeaGreen" )
        val lock = lock().rotateX(180).bottomTo( tube.top ).color( "DarkSeaGreen" )
        val handle = handle().rotateX(90).centerY().bottomTo( caddy.top - 17 ).color("LightGreen")

        return six + caddy + handle + lock
    }

}