/Games/PokerChipHolder.foocad

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.
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
}
}

