Exit Full View
Up

/Hardware/WebbingClip.foocad

WebbingClip

Various parts for attaching webbing, found on ruck sacks etc. All parts are customisable, for use with various thickness of webbing.

I started off creating the common quick-release clip, however, while checking out existing solutions, I realised that the "Double D" is much strong, and simpler than the quick-release clip.

So despite spending quick a long time perfecting the quick-release clip, I doubt I'll use it. Double D is stronger and velco is quicker ;-)

Both halves of the quick-release clip are printed flat on the bed, so that the forces do NOT act across the layer lines. The prongs snapping in tension is the expected failure mode.

The male part of the quick-release clip must NOT use PLA (it is too stiff, and brittle). I've been using ABS during testing, but PETG should work well too.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.screws.v3.Screws.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.arrange.v1.Arrange.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*

class WebbingClip : ModelWithSetup {

    @Custom
    var webbingWidth = 25
    @Custom
    var slotSize = 3.8

    @Custom
    var maleThickness = 4

    @Custom
    var femaleThickness = Vector2(3, 1.75)

    @Custom( about="X for the clearance of 2D shapes, Y for the clearance in the 3rd dimension" )
    var clearance = Vector2(0.15, 0.3)

    var prongLength = 20

    var guideWidth = 5.0

    var chamfer = 0.75

    var tieType = 1

    var _svg : SVGDocument = null 
    var _scale = 1.0
    var _offset = Vector2(0,0)
    override fun setup() {
        _svg = SVGDocument( "WebbingClip.svg" )
        _scale = prongLength / _svg["prong"].size.x
        _offset = Vector2( -_svg["prong"].left, -_svg["prongGeometry"].back)
    }

    fun fromSVG( name : String ) = _svg[name].translate(_offset).scale(_scale).toPolygon()

    fun tie() = if (tieType == 1) singleTie() else doubleTie()

    fun singleTie() : Shape2d {
        val outside = Square( slotSize + maleThickness * 2, webbingWidth + maleThickness*2 )
            .centerY()
            .roundAllCorners(maleThickness*0.5)
            .rightTo(0.01)
        return outside - outside.offset( - maleThickness ).roundAllCorners(chamfer)
    }

    fun doubleTie() : Shape2d {
        return singleTie().translateX( -maleThickness - slotSize ).also()
    }

    fun alignY( shapeName : String ) : Shape2d {
        return fromSVG( shapeName ).translateY( webbingWidth/2 )
    }

    @Piece
    fun male() : Shape3d {

        val tie = tie()
        val prongs = alignY("prong").mirrorY().also()
        val guide = Square( prongs.size.x, guideWidth ).centerY()
            .roundCorners( listOf<int>(1,2), guideWidth/2-0.5 )

        val shape = (prongs + guide + tie)

        return shape.chamferedExtrude(maleThickness,chamfer)
    }

    @Piece
    fun female() : Shape3d {
        val height = femaleThickness.y*2 + maleThickness + clearance.y * 2
        val width = fromSVG("prong").size.x + femaleThickness.x + 1

        val geometry = alignY( "prongGeometry" )
        val bodyRect = Square(width, (geometry.back + clearance.x + femaleThickness.x)*2)
            .centerY()
        val base = bodyRect.roundAllCorners(2)
            .chamferedExtrude( femaleThickness.y, chamfer, 0 ) +
            bodyRect.scaleX(0.5).roundAllCorners(2)
                .chamferedExtrude( femaleThickness.y + maleThickness + clearance.y*2, chamfer, 0)


        val cutout = alignY( "femaleCutout" )
            .mirrorY().also()
            .chamferedExtrude( height + 2, -chamfer ).bottomTo(-0.01)

        val middleRect = Square( bodyRect.size.x + 2, bodyRect.size.y -femaleThickness.x*2 )
            .centerY()
            .leftTo( base.left )
        val middleCorners = Square(7).rotate(-110)
            .translateX( middleRect.left ).translateY( middleRect.back + femaleThickness.x*0.7)
            .mirrorY().also()
        val middle = (middleRect + middleCorners)
            .extrude( maleThickness + clearance.y * 2 )
            .bottomTo( femaleThickness.y )

        //return middle
        val guideShape = Square( base.size.x, femaleThickness.x)
            .frontTo( guideWidth/2 + clearance.x )
            .mirrorY().also()
        val guide = guideShape
            .extrude( height - femaleThickness.y ).bottomTo(femaleThickness.y/2)

        val topShape = guideShape.boundingSquare() +
            (bodyRect.roundAllCorners(2) intersection Square( cutout.left+chamfer, 100 ).centerY())
        val top = topShape
            .chamferedExtrude( femaleThickness.y, 0, chamfer )
            .topTo( height )
            
        // This allows the piece to be printed flat, so that the piece is strong.
        // (printing sitting up would be very weak).
        val cutoutTopShape = Square( base.right - femaleThickness.x - cutout.left, guide.front + base.back )
            .leftTo( cutout.left + chamfer ).frontTo( -guide.front )

        val cutoutTop = cutoutTopShape.mirrorY().also()
            .chamferedExtrude(femaleThickness.y+0.02, 0, -chamfer)
            .bottomTo(height - femaleThickness.y-0.01)
            .color("Blue")

        val tie = tie().mirrorX().leftTo(base.right - femaleThickness.x)
        val tieThickness = Math.min( top.top, maleThickness*1.5)
        val tie3d = tie
            .chamferedExtrude( tieThickness, chamfer )
            
        val topBridgeShape = Square(bodyRect.size.x, alignY( "femaleCutout" ).front*2).roundAllCorners(2)
            .centerY()
        val topBridges = topBridgeShape
            .chamferedExtrude(femaleThickness.y-0.5, 0, chamfer)
            .topTo(height) +
            (topBridgeShape intersection tie.boundingSquare()).extrude( 0.5 + height - femaleThickness.y )


        return base - middle + guide + tie3d + top + topBridges - cutout //+ cutoutTop
        // - tieOverhang
    }

    @Piece
    fun maleAndFemale() = arrangeX(2, male(), female() )

    @Piece
    fun previewMaleAndFemale() : Shape3d {
        val male = male().translateZ( femaleThickness.y + clearance.y ).color("Green")
        val female = female()
        return male + female
    }

    @Piece
    fun tensioner() : Shape3d {
        val profile = doubleTie().center()
        val half = profile.chamferedExtrude( maleThickness, 0, chamfer ).rotateY(12)
            .intersection( Cube( 100 ).centerY() )

        return half.mirrorX().also()
    }

    @Piece
    fun tensioner2() = tensioner( 6, false )

    @Piece
    fun tensioner3() = tensioner( 6, true )

    fun tensioner( height : double, extraPost : bool ) : Shape3d {
        val extraSize : double = if (extraPost) {
            slotSize + maleThickness
        } else {
            slotSize
        }

        val side = Square( slotSize * 2 + maleThickness * 3 + extraSize, height )
            .roundCorners(listOf<int>( 0, 3 ), 1)
            .chamferedExtrude( femaleThickness.x, 1, 0 )
            .rotateX(90)
            .bottomTo(0)
            .frontTo( webbingWidth/2 )
            .leftTo( -slotSize - maleThickness*2 )

        val sides = side.mirrorY().also()
            
        val postA = Square(maleThickness, maleThickness )
            .roundAllCorners(1)
            .extrude(webbingWidth)
            .rotateX(90)
            .centerXY()
            .rightTo( - slotSize - maleThickness ).centerZTo(side.middle.z)

        val postB = if ( extraPost ) {
            postA.rightTo( 0 ).scaleZ(0.5).centerZTo(side.middle.z)
        } else {
            Cube(0)
        }

        val postD = Triangle( maleThickness, height )
            .extrude(webbingWidth)
            .rotateX(90)
            .centerXY()
            .mirrorX()
            .rightTo( sides.right )
            .mirrorZ().bottomTo(0)

        val postC = Triangle( maleThickness * 1.2 ).scaleY(0.6)
            .extrude(webbingWidth)
            .rotateX(90)
            .centerXY()
            .rightTo( postD.left - slotSize )


        val lever = Circle( webbingWidth/2 )
            .chamferedExtrude( femaleThickness.y, 0.75, 0 )
            //.translateX(  )
            .intersection( Cube( 100 ).centerY().translateX( webbingWidth * 0.25 ) )
            .leftTo(postD.right)

        return sides + postA + postB + postC + postD + lever
    }

    @Piece
    fun d() : Shape3d {
        val radius = 1
        val shape = PolygonBuilder().apply {
            moveTo( 0, webbingWidth/2 )
            circularArcTo( Vector2(0, -webbingWidth/2), webbingWidth*0.7, false, false )
            lineBy( -webbingWidth*0.3, 0 )
            lineBy( 0, webbingWidth )
        }.build()
        Quality.increase(2)
        val curve = shape
            .roundCorner(-1, radius)
            .roundCorner(2, radius)
            .roundCorner(1, radius)
            .roundCorner(0, radius)
        val outer = curve.offset( maleThickness )
        Quality.reset()
        return (outer - curve).chamferedExtrude( maleThickness, chamfer )
    }

    @Piece
    fun doubleD() = d().rightTo(0).leftTo(1).also()

    @Piece
    fun sideHook() : Shape3d {
        val regular = Square( slotSize* 2.5 + maleThickness * 3, webbingWidth + maleThickness*3 )
            .centerY().translateY( -maleThickness/2 )
            .roundAllCorners(3)

        val slot = Square( slotSize, webbingWidth )
            .centerY()
            .roundAllCorners(1)
            .leftTo( regular.left + maleThickness )

        val slot2 = Square( slotSize*1.5, webbingWidth )
            .centerY()
            .roundAllCorners(1)
            .rightTo( regular.right - maleThickness )

        val foo = 1
        val gap = Triangle(foo,maleThickness*3).mirrorY().mirrorX().also()
            .centerXTo(slot2.left+foo/4)
            .backTo( regular.back + maleThickness )

        val hump = Circle(maleThickness*2)
            .frontTo(regular.front)
            .leftTo(regular.right - maleThickness*2 )

        return (regular.hull(hump) - slot - slot2 - gap )
            .chamferedExtrude( maleThickness*2, 0.5 )
    }


    @Piece
    fun passthrough() : Shape3d {
        val tie = tensioner()

        val inside = (
            Square( maleThickness + clearance.x*2, tie.size.y + clearance.x*2 ).center() +
            Square( maleThickness + clearance.x*2 + slotSize*2, webbingWidth + clearance.x*2 ).center()
        ).roundAllCorners(1)

        val outside = Square( inside.size.x + maleThickness*2 + clearance.x*2, inside.size.y + maleThickness*2 + clearance.x*2 )
            .roundAllCorners(3)
            .centerTo( inside.middle )


        val extra = (outside - inside).leftTo( tie.right - maleThickness )
        return tie + extra.chamferedExtrude( maleThickness, chamfer )
    }

    @Piece
    fun bridge() : Shape3d {
        return bridge( 15, 20, 0 )
    }

    @Piece
    fun bridgeWithGuide() = bridge( 15, 20, 8 ).mirrorZ().bottomTo(0)

    fun bridge( extra : double, width : double, guideHeight : double ) : Shape3d {

        val main = Square( width, webbingWidth + extra*2 ).center()
            .roundAllCorners( width/3 )
            .chamferedExtrude( slotSize + maleThickness + guideHeight, chamfer, 0 )
    
        val slotChamfer = 3
        val gap = Square( slotSize, webbingWidth )
            .center()
            .chamferedExtrude( width + slotChamfer, -slotChamfer )
            .rotateY(90)
            .centerX()
            .topTo( main.top + slotChamfer + 0.01 )

        val guideGap = if (guideHeight > 0) {
            Square( guideHeight, webbingWidth )
                .center()
                .chamferedExtrude( width + slotChamfer, -slotChamfer )
                .rotateY(90)
                .centerX()
                .bottomTo( -slotChamfer - 0.01 )
        } else {
            null
        }

        val screwHole = Countersink()
            .translateY(main.size.y / 2 - extra /2 )
            .mirrorZ()
            .mirrorY().also()
    
        return main - gap - guideGap - screwHole
    }



    // Similar to 'bridge', but attached the other way up, so that the webbing
    // just site between the posts, rather than *threaded* through a slot.
    @Piece
    fun guide() = guide( Vector3(15, 10, 8 ) )

    fun guide( size : Vector3 ) : Shape3d {
        val post = Square( size.x, size.y ).center()
            .roundAllCorners(2)
            .extrude( size.z )

        val hole = Countersink()
            .topTo(post.top)

        val base = Square( size.x, webbingWidth + 4 ).center()
            .extrude(1)
        val posts = (post - hole).frontTo( webbingWidth/2 + 0.5 )
            .mirrorY().also()
        return base + posts
    }

    override fun build() : Shape3d {
        return arrangeY(10,
            arrangeX( 10, bridge(), guide(), bridgeWithGuide() ).centerX(),
            arrangeX( 10, male(), female() ).centerX(),
            arrangeX( 10, tensioner(), tensioner2(), tensioner3() ).centerX(),
            arrangeX( 10, doubleD(), sideHook() ).centerX()
        ).centerY()
    }

}