import uk.co.nickthecoder.foocad.extras.v1.* import static uk.co.nickthecoder.foocad.extras.v1.Extras.* import uk.co.nickthecoder.foocad.compound.v1.* import uk.co.nickthecoder.foocad.cup.v1.* import static uk.co.nickthecoder.foocad.cup.v1.Cup.* import uk.co.nickthecoder.foocad.smartextrusion.v1.* import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.* import static uk.co.nickthecoder.foocad.extras.v1.Extras.* class CornerBin : ModelWithSetup { // This is close to the max print area of a Qidi Plus 3 @Custom( about="Size of tube sections. NOTE. The top is larger than this." ) var size = Vector2( 266, 240 ) @Custom( about="Radius of the 3 rounded corners" ) var round = 18 @Custom( about="A straight side in the Y direction in the back right corner" ) val shortSide = 10 @Custom( about="Thickness of the main walls" ) var thickness = 1.8 @Custom( about="Thickness of the bottom of the bin (piece `base`" ) var baseThickness = 1.8 @Custom( about="Height of the lid" ) var lidThickness = 6 @Custom( about="Z Thickness of the lip of piece `top`" ) var topThickness = 2 @Custom( about="X dimension of piece `lidTrim`" ) var trimWidth = 12 @Custom( about="Height of piece `base`" ) var baseHeight = 100 @Custom( about="Use the Customiser to make striped patterns of verious heights" ) var sectionHeight = 100 @Custom( about="Clearance of the fit between each vertical section" ) var clearance = 0.2 @Custom( about="Size of the rounded chamfer at the bottom of the bin" ) var baseChamfer = 10 @Custom( about="Size of the connections between the vertical sections" ) var lipSize = Vector2(2, 10) @Custom( about="Gap for the bag to loop back round" ) var bagClearance = 2 @Custom var lidClearance = 1.0 @Custom( about="Length and diameter of the holes for the hinges' holes" ) var hingeRodSize = Vector2( 20, 2 ) @Custom( about="Additional height of the top compared to the bagTidy" ) var topExtraHeight = 10 @Custom( about="" ) var stopWidth = 3 override meth setup() { // Uncomment for a smoother (less facetted) surface. // Quality.increase(3) } meth shape() = PolygonBuilder().apply { moveTo( round, 0 ) circularArcTo( 0, -round, round, false, true ) lineTo( 0, - size.y + round ) // Left edge circularArcTo( round, -size.y, round, false, true ) bezierBy( // Main curve Vector2( size.x/2, 0 ), Vector2( 0, size.y/2 ), Vector2( size.x - round, size.y - round - shortSide ) ) lineBy( 0, shortSide ) circularArcTo( size.x - round, 0, round, false, true ) }.build().center() meth outsideLip() : ProfileEdge { val profile = PolygonBuilder().apply { moveTo( 0,0 ) lineBy( thickness + clearance, 0 ) lineBy( 0, lipSize.y - lipSize.x ) lineBy( lipSize.x, lipSize.x ) }.buildPath() return ProfileEdge( profile ) } meth insideLip() : ProfileEdge { val profile = PolygonBuilder().apply { moveTo(0,0) lineBy( -lipSize.x, lipSize.x ) lineBy( 0, lipSize.y + lipSize.x /2 ) }.buildPath() return ProfileEdge( profile ) } meth bottomLip() : ProfileEdge { val delta = lipSize.x val profile = PolygonBuilder().apply { moveTo(0,0) lineBy( -delta, delta ) lineBy( 0, 1 ) lineBy( delta, delta ) lineBy( 0, lipSize.y - lipSize.x ) }.buildPath() return ProfileEdge( profile ) } // Position of the hinges relative to the lid on the build plate. meth hingePosition() = Vector3( -size.x / 2 - thickness - bagClearance + trimWidth + 0.5, size.y/2 - round- shortSide, lidThickness/2 ) // Removed from lidTrim and lid. // See About for details on assmbling the hinge. meth hingeHole() : Shape3d { val free = Cylinder( hingeRodSize.x, hingeRodSize.y/2 ).hole().bottomTo( -trimWidth +3 ) val circle = Circle( hingeRodSize.y/2 + 1 ).hole() val exposed = circle.hull( circle.translateX(10) ) .extrude( hingeRodSize.x ) .bottomTo( trimWidth ) return (free + exposed) .rotateY(90) .translate( hingePosition() ) } @Piece @Slice( brimWidth=3 ) meth hingeCover() : Shape3d { val overlap = 2 val radius = hingeRodSize.y/2 + 1 - 0.1 val shape = Circle( radius ).hull( Square( lidThickness/2-0.3, radius*2 ).centerY() ) val base = hingeCoverPlain().mirrorZ().bottomTo(0) val stop = shape .extrude( trimWidth ) .rotateY(90) .rightTo( base.right - overlap ) return base + stop.bottomTo( base.top ) } @Piece @Slice( brimWidth=3 ) meth hingeCoverRecessed() : Shape3d { val overlap = 2 val radius = hingeRodSize.y/2 + 1 - 0.1 val shape = Circle( radius ).hull( Square( lidThickness/2-0.3, radius*2 ).centerY() ) val base = hingeCoverPlain().mirrorZ().bottomTo(0) val extra = Square( base.size.x, radius*2-0.2 ) .center() .roundAllCorners(0.5) .extrude( 1 ) .bottomTo( base.top ) return base + extra } @Piece meth hingeCoverPlain() : Shape3d { val overlap = 2 val radius = hingeRodSize.y/2 + 1 - 0.1 val shape = Circle( radius ).hull( Square( lidThickness/2-0.3, radius*2 ).centerY() ) val base = Square( hingeRodSize.x+overlap*2, hingeRodSize.y + overlap*2 ) .center() .roundAllCorners(1) .smartExtrude( 1 ) .top( Chamfer(1) ) .margin( Vector3(-overlap, -overlap, 0) ) return base } @Piece meth foot() = shape() .offset( - baseChamfer/2 -1 ) .outline( 0, 10 ) .smartExtrude( 1.2 ) .bottom( Chamfer( 1 ) ) .mirrorZ() .bottomTo(0) @Piece @Slice( brimWidth=11 ) meth base() : Shape3d { val base = shape() .cup( baseHeight, thickness ) .baseThickness( baseThickness ) .bottom( roundedChamfer( baseChamfer ) ) .insideTop( insideLip() ) .outsideTop( outsideLip() ) return base } @Piece @Slice( brimWidthInterior=10 ) meth section60() = section( 60 ) @Piece @Slice( brimWidthInterior=10 ) meth section80() = section( 80 ) @Piece @Slice( brimWidthInterior=10 ) meth section160() = section( 160 ) @Piece @Slice( brimWidthInterior=10 ) meth section200() = section( 200 ) @Piece @Slice( brimWidthInterior=10 ) meth section() = section( sectionHeight ) meth section(height : double) : Shape3d { return shape() .outline( 0, thickness ) .smartExtrude( height ) .insideBottom( bottomLip() ) .insideTop( insideLip() ) .outsideTop( outsideLip() ) } @Piece @Slice( brimWidthInterior=10 ) meth printBagTidy() = bagTidy().rotateX(180).bottomTo(0) // Loop the bin liner around this piece, so that the lose ends are // hidden and out of the way. // The ends of the liner are between the OUTSIDE of this piece, // and the INSIDE of the bin @Piece( print="printBagTidy" ) meth bagTidy() : Shape3d { val height = 10 val shape = shape().offset( bagClearance/2 ) val connection = shape .outline( 0, thickness ) .smartExtrude( bottomLip().profile.size.y ) .insideBottom( bottomLip() ) .outsideBottom( Fillet(0.8) ) .outsideTop( Chamfer(0.8) ) //.color("Orange") val main = shape .outline( 0, thickness*2 + bagClearance ) .smartExtrude( baseThickness + 1 ) .top( Chamfer(0.8) ) .topTo( connection.top ) //.color("Green") return connection + main } /** Hides the `bagTidy`, so that the bag isn' visible when the bin is closed. Glue `lidTrim` to the top, with the `lid` in position (including the screws, which form the hinge). After gluing, `top`, `lidTrim` and `lid` will be inseperable. */ @Piece @Slice( brimWidth=3.5, brimWidthInterior=10 ) meth printTop() = top().rotateX(180).bottomTo(0) meth trimHoles() : Shape3d { return Cylinder( lidThickness*2 - 2, 1 ) .translate( hingePosition() ) .translateX( -trimWidth/2 + 1) .centerZ() .translateY(10) .translateY(-22).also() .mirrorX().also() } @Piece( print="printTop" ) meth top() : Shape3d { val lid = lid() val outsideShape = shape().offset( thickness + bagClearance ) val insideShape = shape().offset( thickness + bagClearance - trimWidth - stopWidth - lidClearance ) val height = bagTidy().size.z + topExtraHeight // Hides the `bagTidy` piece, with extra 5mm to spare. val cutoutFront = hingePosition().y - lidThickness/2 - lidClearance val cutoutBack = size.y/2 + thickness + bagClearance - trimWidth val cutoutY = cutoutBack - cutoutFront val cutoutWidth = lid.size.x - trimWidth*2 val cutoutRadius = round - (shape().size.x - cutoutWidth )/2 val cutout = Square( cutoutWidth, cutoutY ) .roundCorner(3, cutoutRadius ) .roundCorner(2, cutoutRadius ) .centerX() .frontTo( cutoutFront ) val top = (outsideShape - insideShape - cutout) .smartExtrude( topThickness ) .top( roundedChamfer( baseThickness ) ) val sides = outsideShape.outline( 0, thickness ) .smartExtrude( height ) .insideTop( Chamfer(-1, 1) ) .bottom( Chamfer(0.5) ) .topTo( 0 ) val stops = Trapezium( 10, 10 ) .angle(9) .backTo(0) .roundCorner(1,2) .roundCorner(0,2) .smartExtrude( stopWidth ) .rotateX(90) .rotateZ(90) .backTo( cutout.front ) .leftTo( insideShape.right ) .topTo( top.bottom ) .mirrorX().also() return top + sides + stops - trimHoles() } // The lid is printed upside down, so the smoothest surface is on show. @Piece @Slice( brimWidth=5 ) meth printLid() = lid().rotateX(180).bottomTo(0) @Piece( print="printLid" ) @Slice( brimWidth=3 ) meth lid() : Shape3d { val outsideShape = shape().offset( thickness + bagClearance ) val insideShape = shape().offset( thickness + bagClearance - trimWidth - lidClearance ) // The lip follows the same shape as the lid itself, // but to make it look nicer, we use a Circle as an envelope (intersection) // I used trial an error to get a pleasing result. // Therefore, if you change the basic shape, you may need to tinker with the constants. val lipDiameter = size.x * 0.6 val lipCircle = Circle( lipDiameter/2 ).translate( -size.x*0.05, -size.y*0.2 ) val lipShape = outsideShape.intersection( lipCircle ) .offset(10) .intersection( Square(size.x, size.y*2).center() ) val outside = (outsideShape + lipShape) .smartExtrude( lidThickness ) .outsideTop( roundedChamfer( lidThickness - 1 ) ) val inside = insideShape .smartExtrude( lidThickness ) .top( Chamfer(1) ) val removeOutsideBack = Cube( outside.size.x + 1) .center() .frontTo(0) .rotateX(-45) .translateY( size.y/2 - round - shortSide - 30 - 1 ) val result = outside - removeOutsideBack + inside - hingeHole().mirrorX().also() return result // + lipCircle.extrude(10).previewOnly() } @Piece @Slice( brimWidthInterior=5 ) meth printLidTrim() : Shape3d { val trim = lidTrim().rotateX(180).bottomTo(0) val allowInteriorBrim = Cube( trim.size.x - trimWidth, 0.4, 0.2 ) .centerX() .backTo( trim.back - trim.size.z * 1.25 ) return trim + allowInteriorBrim } // Glued/screwed onto `top` piece. @Piece( print="printLidTrim" ) @Slice( brimWidthInterior=5 ) fun lidTrim() : Shape3d { val ledgeClearance = 0.3 val ledgeHeight = 2 val ledgeOffset = 1.5 val outsideShape = shape().offset( thickness + bagClearance ) val trim = outsideShape.outline( 0, trimWidth ) .smartExtrude( lidThickness + ledgeHeight + ledgeClearance ) .outsideTop( roundedChamfer( lidThickness - 1 ) ) .insideTop( Chamfer( ledgeHeight/2 ) ) val ledgeY = size.y/2 - round - shortSide - 30 val ledgeShape = Square( size.x + ledgeOffset*2, round + ledgeOffset*2 + 2 ) .roundCorner(3, round) .roundCorner(2, round) .centerX() .backTo( size.y / 2 + ledgeOffset + 0.1 ) .intersection( outsideShape ) val ledge = ledgeShape .smartExtrude( ledgeHeight ) .top( Chamfer( ledgeHeight/2 ) ) .topTo( trim.top ) val roundEnvelope = PolygonBuilder().apply { moveTo( 0, ledgeY ) lineTo( 0, trim.back ) lineTo( trim.size.z, trim.back ) lineTo( trim.size.z, ledgeY + trim.size.z ) lineTo( lidThickness, ledgeY + lidThickness ) }.build() // Comment the next line if you wish. YMMV. // With the rounding, there is a steeper overhang (twice as steep as a chamfer), // but a nicer rounding where the trim is higher than the lid. .roundCorner(1, 5, 1) .extrude(trim.size.x + 2) .rotateY(-90).centerX() .frontTo( ledgeY ) // To see how `roundEnvelope` works : // return trim + roundEnvelope.previewOnly() + return trim.intersection(roundEnvelope) + ledge - hingeHole().mirrorX().also() - trimHoles() } @Piece( printable=false ) meth topAssembly() : Shape3d { val lid = lid() .translateZ(0.1) .color("Green") val trim = lidTrim() .color("Orange") val top = top().topTo( trim.bottom-0.2 ).previewOnly() val axis = Cube( top.size.x + 2, 0.5, 0.5 ) .center() .translate( hingePosition() ).centerX() .color("Red") val hingeHole = hingeHole().previewOnly() val hingeCover = hingeCover() .rightTo( hingeHole.right + 2 ) .translateZ(-1) .centerYTo( hingeHole.middle.y ) .previewOnly() val angle = 0 // 100 // 100° is fully open. 0° is fully closed. val rotatedLid = (lid + hingeCover + hingeHole.mirrorX()).translate( -hingePosition() ) .rotateX( -angle ) .translate( hingePosition() ) return rotatedLid + trim + top + axis } @Piece( printable = false ) override meth build() : Shape3d { val dark = "SteelBlue" val light = "LightSteelBlue" val base = base() .color( dark ) val section1 = section(200).color( light ) .bottomTo( base.top - lipSize.y + clearance ) val section2 = section(80).color( dark ) .bottomTo( section1.top - lipSize.y + clearance ) val section3 = section(160).color( light ) .bottomTo( section2.top - lipSize.y + clearance ) val section4 = section(60).color( dark ) .bottomTo( section3.top - lipSize.y + clearance ) val bagTidy = bagTidy() .bottomTo( section4.top - lipSize.y + clearance ) .color( dark ) val top = top() .topTo( bagTidy.top + topThickness + bagClearance ) .color(dark) val lid = lid() .color(dark) val lineOfRotation = Cube( top.size.x + 2, 0.5, 0.5 ) .center().translate( hingePosition() ).centerX() .color("Red") val angle = 60 // 100 // 100° is fully open. 0° is fully closed. val rotatedLid = lid.translate( -hingePosition() ) .rotateX( -angle ) .translate( hingePosition() ) .translateZ( top.top + clearance ) val lidTrim = lidTrim() .bottomTo( top.top + clearance ) .color(dark) val foot = foot() .mirrorZ() .topTo( base.bottom ) .color( light ) return Compound().apply { + foot + base + section1 + section2 + section3 + section4 + bagTidy + top + lidTrim + rotatedLid + lineOfRotation }.build() } }