import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* class FruitShaper : Model { @Custom( about = "Diameter and length of the tube" ) var size = Vector2( 30, 110 ) @Custom( about="Thickness of the tube" ) var thickness = 3 @Custom( about="Width and thickness of the ribs" ) var ribSize = Vector2( 5, 2.0 ) @Custom( about = "How much bigger are the ribs compared to the tube" ) var slack = 0.4 /** Override this method for other shapes. See HeartFruitShaper.foocad */ fun plainProfile() : Shape2d = Square( 100 ).roundAllCorners(10).center() fun scaledProfile() : Shape2d { val plain = plainProfile() val scaled = plain.scale( size.x / plain.size.x ) return scaled } fun tabbedProfile() : Shape2d { val scaled = scaledProfile().offset( thickness ) return scaled + Square( 7, scaled.size.y + 8 ).center() } @Piece fun bothHalves() = tube( true ).translateX(1) + tube( false ) @Piece fun halfA() = tube( true ) @Piece fun halfB() = tube( false ) fun tube( rightPart : bool ) : Shape3d { val inside = scaledProfile() val outside = tabbedProfile() val ring = outside - inside val dy = ring.size.y / 20 val cut = PolygonBuilder().apply { moveTo(0,0) for ( i in 0..10) { lineBy( dy, dy ) lineBy( -dy, dy ) } lineBy( ring.size.x, 0 ) lineTo( ring.size.x, 0 ) }.build() .roundAllCorners( 0.5 ) .frontTo( ring.front -dy).leftTo(-dy/2) return if ( rightPart ) { (ring / cut.offset(-slack/2)).extrude( size.y ).color("Green") } else { (ring - cut.offset(slack/2)).extrude( size.y ).color("Blue") } } @Piece fun rib() : Shape3d { val profile = tabbedProfile() val ring = profile.offset( ribSize.x + slack ) - profile.offset( slack ) return ring.extrude( ribSize.y ).color("Orange" ) } override fun build() : Shape3d { val rib : Shape3d = rib() return tube(true) + tube(false) + rib.repeatZ( 5, size.y / 4 ) } }