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(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( 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() } }