From 26855429d82b26343c6e80e650d9d6997d1ce04e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 27 Mar 2022 19:23:47 -0400 Subject: [PATCH 1/4] move color_this and recolor into color.scad doc tweaks for rounding --- attachments.scad | 56 ----------------------------- color.scad | 93 +++++++++++++++++++++++++++++++++++++++--------- rounding.scad | 83 +++++++++++++++++++++++++++++------------- 3 files changed, 134 insertions(+), 98 deletions(-) diff --git a/attachments.scad b/attachments.scad index a8db9c6..8a70e2b 100644 --- a/attachments.scad +++ b/attachments.scad @@ -819,62 +819,6 @@ module hulling(a) } -// Module: recolor() -// Usage: -// recolor([c]) {...} -// Topics: Attachments -// See Also: color_this(), tags(), hide(), show(), diff(), intersect() -// Description: -// Sets the color for children and all their descendants. This only works with attachables and you cannot -// have any color() modules above it in any parents, only other recolor() or color_this() modules. -// This works by setting the special `$color` variable. -// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// c = Color name or RGBA vector. Default: The default color in your color scheme. -// Example: -// cuboid([10,10,5]) -// recolor("green")attach(TOP,BOT) cuboid([9,9,4.5]) -// attach(TOP,BOT) cuboid([8,8,4]) -// recolor("purple") attach(TOP,BOT) cuboid([7,7,3.5]) -// attach(TOP,BOT) cuboid([6,6,3]) -// recolor("cyan")attach(TOP,BOT) cuboid([5,5,2.5]) -// attach(TOP,BOT) cuboid([4,4,2]); -module recolor(c="default") -{ - $color=c; - children(); -} - - -// Module: color_this() -// Usage: -// color_this([c]) {...} -// Topics: Attachments -// See Also: tags(), recolor() -// Description: -// Sets the color for children at one level, reverting to the previous color for further descendants. -// This works only with attachables and you cannot have any color() modules above it in any parents, -// only recolor() or other color_this() modules. This works using the `$color` and `$save_color` variables. -// . -// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// c = Color name or RGBA vector. Default: the default color in your color scheme -// Example: -// cuboid([10,10,5]) -// color_this("green")attach(TOP,BOT) cuboid([9,9,4.5]) -// attach(TOP,BOT) cuboid([8,8,4]) -// color_this("purple") attach(TOP,BOT) cuboid([7,7,3.5]) -// attach(TOP,BOT) cuboid([6,6,3]) -// color_this("cyan")attach(TOP,BOT) cuboid([5,5,2.5]) -// attach(TOP,BOT) cuboid([4,4,2]); -module color_this(c="default") -{ - $save_color=default($color,"default"); - $color=c; - children(); -} - - // Module: hide() // Usage: // hide(tags) {...} diff --git a/color.scad b/color.scad index b820f02..3bb19f4 100644 --- a/color.scad +++ b/color.scad @@ -11,15 +11,69 @@ use - // Section: Coloring Objects +// Module: recolor() +// Usage: +// recolor([c]) {...} +// Topics: Attachments +// See Also: color_this() +// Description: +// Sets the color for attachable children and all their descendants. This only works with attachables and you cannot +// have any color() modules above it in any parents, only other recolor() or color_this() modules. +// This works by setting the special `$color` variable, which attachable objects make use of to set the color. +// Arguments: +// c = Color name or RGBA vector. Default: The default color in your color scheme. +// Example: +// cuboid([10,10,5]) +// recolor("green")attach(TOP,BOT) cuboid([9,9,4.5]) +// attach(TOP,BOT) cuboid([8,8,4]) +// recolor("purple") attach(TOP,BOT) cuboid([7,7,3.5]) +// attach(TOP,BOT) cuboid([6,6,3]) +// recolor("cyan")attach(TOP,BOT) cuboid([5,5,2.5]) +// attach(TOP,BOT) cuboid([4,4,2]); +module recolor(c="default") +{ + $color=c; + children(); +} + + +// Module: color_this() +// Usage: +// color_this([c]) {...} +// Topics: Attachments +// See Also: recolor() +// Description: +// Sets the color for children at one level, reverting to the previous color for further descendants. +// This works only with attachables and you cannot have any color() modules above it in any parents, +// only recolor() or other color_this() modules. This works using the `$color` and `$save_color` variables, +// which attachable objects make use of to set the color. +// Arguments: +// c = Color name or RGBA vector. Default: the default color in your color scheme +// Example: +// cuboid([10,10,5]) +// color_this("green")attach(TOP,BOT) cuboid([9,9,4.5]) +// attach(TOP,BOT) cuboid([8,8,4]) +// color_this("purple") attach(TOP,BOT) cuboid([7,7,3.5]) +// attach(TOP,BOT) cuboid([6,6,3]) +// color_this("cyan")attach(TOP,BOT) cuboid([5,5,2.5]) +// attach(TOP,BOT) cuboid([4,4,2]); +module color_this(c="default") +{ + $save_color=default($color,"default"); + $color=c; + children(); +} + + // Module: rainbow() // Usage: // rainbow(list) ... // Description: -// Iterates the list, displaying children in different colors for each list item. -// This is useful for debugging lists of paths and such. +// Iterates the list, displaying children in different colors for each list item. The color +// is set using the color() module, so this module is not compatible with {{recolor()}} or +// {{color_this}}. This is useful for debugging regions or lists of paths. // Arguments: // list = The list of items to iterate through. // stride = Consecutive colors stride around the color wheel divided into this many parts. @@ -54,27 +108,29 @@ module rainbow(list, stride=1, maxhues, shuffle=false, seed) // Function&Module: HSL() // Usage: // HSL(h,[s],[l],[a]) ... -// rgb = HSL(h,[s],[l]); +// rgb = HSL(h,[s],[l],[a]); // Description: -// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. -// When called as a module, sets the color to the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. +// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. If you supply +// the `a` value then you'll get a length 4 list [R,G,B,A]. +// When called as a module, sets the color using the color() module to the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. // Arguments: // h = The hue, given as a value between 0 and 360. 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta. // s = The saturation, given as a value between 0 and 1. 0 = grayscale, 1 = vivid colors. Default: 1 // l = The lightness, between 0 and 1. 0 = black, 0.5 = bright colors, 1 = white. Default: 0.5 -// a = When called as a module, specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 +// a = Specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 // Example: // HSL(h=120,s=1,l=0.5) sphere(d=60); // Example: // rgb = HSL(h=270,s=0.75,l=0.6); // color(rgb) cube(60, center=true); -function HSL(h,s=1,l=0.5) = +function HSL(h,s=1,l=0.5,a) = let( h=posmod(h,360) ) [ - for (n=[0,8,4]) let( - k=(n+h/30)%12 - ) l - s*min(l,1-l)*max(min(k-3,9-k,1),-1) + for (n=[0,8,4]) + let(k=(n+h/30)%12) + l - s*min(l,1-l)*max(min(k-3,9-k,1),-1), + if (is_def(a)) a ]; module HSL(h,s=1,l=0.5,a=1) color(HSL(h,s,l),a) children(); @@ -83,23 +139,25 @@ module HSL(h,s=1,l=0.5,a=1) color(HSL(h,s,l),a) children(); // Function&Module: HSV() // Usage: // HSV(h,[s],[v],[a]) ... -// rgb = HSV(h,[s],[v]); +// rgb = HSV(h,[s],[v],[a]); // Description: -// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and value `v` from the HSV colorspace. -// When called as a module, sets the color to the given hue `h`, saturation `s`, and value `v` from the HSV colorspace. +// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and value `v` from the HSV colorspace. If you supply +// the `a` value then you'll get a length 4 list [R,G,B,A]. +// When called as a module, sets the color using the color() module to the given hue `h`, saturation `s`, and value `v` from the HSV colorspace. // Arguments: // h = The hue, given as a value between 0 and 360. 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta. // s = The saturation, given as a value between 0 and 1. 0 = grayscale, 1 = vivid colors. Default: 1 // v = The value, between 0 and 1. 0 = darkest black, 1 = bright. Default: 1 -// a = When called as a module, specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 +// a = Specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 // Example: // HSV(h=120,s=1,v=1) sphere(d=60); // Example: // rgb = HSV(h=270,s=0.75,v=0.9); // color(rgb) cube(60, center=true); -function HSV(h,s=1,v=1) = +function HSV(h,s=1,v=1,a) = assert(s>=0 && s<=1) assert(v>=0 && v<=1) + assert(is_undef(a) || a>=0 && a<=1) let( h = posmod(h,360), c = v * s, @@ -114,7 +172,8 @@ function HSV(h,s=1,v=1) = : [0,0,0], m=v-c ) - rgbprime+[m,m,m]; + is_def(a) ? point4d(add_scalar(rgbprime,m),a) + : add_scalar(rgbprime,m); module HSV(h,s=1,v=1,a=1) color(HSV(h,s,v),a) children(); diff --git a/rounding.scad b/rounding.scad index 41ef49a..6cb4ea4 100644 --- a/rounding.scad +++ b/rounding.scad @@ -306,21 +306,25 @@ include // $fn=64; // path = [[0, 0],[10, 0],[20, 20],[30, -10]]; // debug_polygon(path); -// //polygon(round_corners(path,cut = [1,3,1,1], method="circle")); +// //polygon(round_corners(path,cut = [1,3,1,1], +// // method="circle")); // Example(2D): The list of factors shows that the problem is in the first two rounding values, because the factors are smaller than one. If we multiply the first two parameters by 0.85 then the roundings fit. The verbose option gives us the same fit factors. // $fn=64; // path = [[0, 0],[10, 0],[20, 20],[30, -10]]; -// polygon(round_corners(path,cut = [0.85,3*0.85,1,1], method="circle", verbose=true)); +// polygon(round_corners(path,cut = [0.85,3*0.85,1,1], +// method="circle", verbose=true)); // Example(2D): From the fit factors we can see that rounding at vertices 2 and 3 could be increased a lot. Applying those factors we get this more rounded shape. The new fit factors show that we can still further increase the rounding parameters if we wish. // $fn=64; // path = [[0, 0],[10, 0],[20, 20],[30, -10]]; -// polygon(round_corners(path,cut = [0.85,3*0.85,2.13, 10.15], method="circle",verbose=true)); +// polygon(round_corners(path,cut = [0.85,3*0.85,2.13, 10.15], +// method="circle",verbose=true)); // Example(2D): Using the `joint` parameter it's easier to understand whether your roundvers will fit. We can guarantee a fairly large roundover on any path by picking each one to use up half the segment distance along the shorter of its two segments: // $fn=64; // path = [[0, 0],[10, 0],[20, 20],[30, -10]]; // path_len = path_segment_lengths(path,closed=true); // halflen = [for(i=idx(path)) min(select(path_len,i-1,i))/2]; -// polygon(round_corners(path,joint = halflen, method="circle",verbose=true)); +// polygon(round_corners(path,joint = halflen, +// method="circle",verbose=true)); // Example(2D): Chamfering, specifying the chamfer width // path = star(5, step=2, d=100); // path2 = round_corners(path, method="chamfer", width=5); @@ -619,16 +623,23 @@ function _rounding_offsets(edgespec,z_dir=1) = // polygon(smooth_path(square(4),size=0.4,closed=true)); // Example(2D): Turning on uniform tangent calculation also changes the end derivatives: // color("green")stroke(square(4), width=0.1); -// stroke(smooth_path(square(4),size=0.4,uniform=true), width=0.1); +// stroke(smooth_path(square(4),size=0.4,uniform=true), +// width=0.1); // Example(2D): Here's a wide rectangle. Using size means all edges bulge the same amount, regardless of their length. +// color("green") +// stroke(square([10,4]), closed=true, width=0.1); +// stroke(smooth_path(square([10,4]),size=1,closed=true), +// width=0.1); +// Example(2D): With relsize the bulge is proportional to the side length. // color("green")stroke(square([10,4]), closed=true, width=0.1); -// stroke(smooth_path(square([10,4]),size=1,closed=true),width=0.1); -// Example(2D): Here's a wide rectangle. With relsize the bulge is proportional to the side length. -// color("green")stroke(square([10,4]), closed=true, width=0.1); -// stroke(smooth_path(square([10,4]),relsize=0.1,closed=true),width=0.1); -// Example(2D): Here's a wide rectangle. Settting uniform to true biases the tangents to aline more with the line sides -// color("green")stroke(square([10,4]), closed=true, width=0.1); -// stroke(smooth_path(square([10,4]),uniform=true,relsize=0.1,closed=true),width=0.1); +// stroke(smooth_path(square([10,4]),relsize=0.1,closed=true), +// width=0.1); +// Example(2D): Settting uniform to true biases the tangents to aline more with the line sides +// color("green") +// stroke(square([10,4]), closed=true, width=0.1); +// stroke(smooth_path(square([10,4]),uniform=true, +// relsize=0.1,closed=true), +// width=0.1); // Example(2D): A more interesting shape: // path = [[0,0], [4,0], [7,14], [-3,12]]; // polygon(smooth_path(path,size=1,closed=true)); @@ -636,11 +647,13 @@ function _rounding_offsets(edgespec,z_dir=1) = // polygon(smooth_path(square(4), size=.25,closed=true)); // Example(2D): Here's the square with a size that's too big to achieve, so you get the maximum possible curve: // color("green")stroke(square(4), width=0.1,closed=true); -// stroke(smooth_path(square(4), size=4, closed=true),closed=true,width=.1); +// stroke(smooth_path(square(4), size=4, closed=true), +// closed=true,width=.1); // Example(2D): You can alter the shape of the curve by specifying your own arbitrary tangent values // polygon(smooth_path(square(4),tangents=1.25*[[-2,-1], [-4,1], [1,2], [6,-1]],size=0.4,closed=true)); // Example(2D): Or you can give a different size for each segment -// polygon(smooth_path(square(4),size = [.4, .05, 1, .3],closed=true)); +// polygon(smooth_path(square(4),size = [.4, .05, 1, .3], +// closed=true)); // Example(FlatSpin,VPD=35,VPT=[4.5,4.5,1]): Works on 3d paths as well // path = [[0,0,0],[3,3,2],[6,0,1],[9,9,0]]; // stroke(smooth_path(path,relsize=.1),width=.3); @@ -716,23 +729,31 @@ function _scalar_to_vector(value,length,varname) = // Example(2D): Specifying pairs of joint values at a path joint creates an asymmetric curve // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; -// stroke(path_join([horiz, vert, -horiz],joint=[[4,1],[1,4]],$fn=16),width=.3); +// stroke(path_join([horiz, vert, -horiz], +// joint=[[4,1],[1,4]],$fn=16),width=.3); // Example(2D): A closed square // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; -// stroke(path_join([horiz, vert, -horiz, -vert],joint=3,k=1,closed=true,$fn=16),closed=true); +// stroke(path_join([horiz, vert, -horiz, -vert], +// joint=3,k=1,closed=true,$fn=16),closed=true); // Example(2D): Different curve at each corner by changing the joint size // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; -// stroke(path_join([horiz, vert, -horiz, -vert],joint=[3,0,1,2],k=1,closed=true,$fn=16),closed=true,width=0.4); +// stroke(path_join([horiz, vert, -horiz, -vert], +// joint=[3,0,1,2],k=1,closed=true,$fn=16), +// closed=true,width=0.4); // Example(2D): Different curve at each corner by changing the curvature parameter. Note that k=0 still gives a small curve, unlike joint=0 which gives a sharp corner. // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; -// stroke(path_join([horiz, vert, -horiz, -vert],joint=3,k=[1,.5,0,.7],closed=true,$fn=16),closed=true,width=0.4); +// stroke(path_join([horiz, vert, -horiz, -vert],joint=3, +// k=[1,.5,0,.7],closed=true,$fn=16), +// closed=true,width=0.4); // Example(2D): Joint value of 7 is larger than half the square so curves interfere with each other, which breaks symmetry because they are computed sequentially // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; -// stroke(path_join([horiz, vert, -horiz, -vert],joint=7,k=.4,closed=true,$fn=16),closed=true); +// stroke(path_join([horiz, vert, -horiz, -vert],joint=7, +// k=.4,closed=true,$fn=16), +// closed=true); // Example(2D): Unlike round_corners, we can add curves onto curves. // $fn=64; // myarc = arc(width=20, thickness=5 ); @@ -740,29 +761,41 @@ function _scalar_to_vector(value,length,varname) = // Example(2D): Here we make a closed shape from two arcs and round the sharp tips // arc1 = arc(width=20, thickness=4,$fn=75); // arc2 = reverse(arc(width=20, thickness=2,$fn=75)); -// stroke(path_join([arc1,arc2]),width=.3); // Join without rounding -// color("red")stroke(path_join([arc1,arc2], 3,k=1,closed=true), width=.3,closed=true,$fn=12); // Join with rounding +// // Without rounding +// stroke(path_join([arc1,arc2]),width=.3); +// // With rounding +// color("red")stroke(path_join([arc1,arc2], 3,k=1,closed=true), +// width=.3,closed=true,$fn=12); // Example(2D): Combining arcs with segments // arc1 = arc(width=20, thickness=4,$fn=75); // arc2 = reverse(arc(width=20, thickness=2,$fn=75)); // vpath = [[0,0],[0,-5]]; // stroke(path_join([arc1,vpath,arc2,reverse(vpath)]),width=.2); -// color("red")stroke(path_join([arc1,vpath,arc2,reverse(vpath)], [1,2,2,1],k=1,closed=true), width=.2,closed=true,$fn=12); +// color("red")stroke(path_join([arc1,vpath,arc2,reverse(vpath)], +// [1,2,2,1],k=1,closed=true), +// width=.2,closed=true,$fn=12); // Example(2D): Here relocation is off. We have three segments (in yellow) and add the curves to the segments. Notice that joint zero still produces a curve because it refers to the endpoints of the supplied paths. // p1 = [[0,0],[2,0]]; // p2 = [[3,1],[1,3]]; // p3 = [[0,3],[-1,1]]; -// color("red")stroke(path_join([p1,p2,p3], joint=0, relocate=false,closed=true),width=.3,$fn=12); +// color("red")stroke( +// path_join([p1,p2,p3], joint=0, relocate=false, +// closed=true), +// width=.3,$fn=12); // for(x=[p1,p2,p3]) stroke(x,width=.3); // Example(2D): If you specify closed=true when the last path doesn't meet the first one then it is similar to using relocate=false: the function tries to close the path using a curve. In the example below, this results in a long curve to the left, when given the unclosed three segments as input. Note that if the segments are parallel the function fails with an error. The extension of the curves must intersect in a corner for the rounding to be well-defined. To get a normal rounding of the closed shape, you must include a fourth path, the last segment that closes the shape. // horiz = [[0,0],[10,0]]; // vert = [[0,0],[0,10]]; // h2 = [[0,-3],[10,0]]; -// color("red")stroke(path_join([horiz, vert, -h2],closed=true,joint=3,$fn=25),closed=true,width=.5); +// color("red")stroke( +// path_join([horiz, vert, -h2],closed=true, +// joint=3,$fn=25), +// closed=true,width=.5); // stroke(path_join([horiz, vert, -h2]),width=.3); // Example(2D): With a single path with closed=true the start and end junction is rounded. // tri = regular_ngon(n=3, r=7); -// stroke(path_join([tri], joint=3,closed=true,$fn=12),closed=true,width=.5); +// stroke(path_join([tri], joint=3,closed=true,$fn=12), +// closed=true,width=.5); module path_join(paths,joint=0,k=0.5,relocate=true,closed=false) { no_module();} function path_join(paths,joint=0,k=0.5,relocate=true,closed=false)= assert(is_list(paths),"Input paths must be a list of paths") From 58eb8a5e0d0c408a92734343f6caccf2880a67bf Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 27 Mar 2022 19:27:08 -0400 Subject: [PATCH 2/4] doc tweak --- attachments.scad | 2 ++ 1 file changed, 2 insertions(+) diff --git a/attachments.scad b/attachments.scad index 8a70e2b..ac89a82 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1206,6 +1206,8 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // `$parent_orient` is set to the parent object's `orient` value. // `$parent_geom` is set to the parent object's `geom` value. // `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size. +// `$color` is used to set the color of the object +// `$save_color` is used to revert color to the parent's color // // Example(NORENDER): Cubical Shape // attachable(anchor, spin, orient, size=size) { From 4e11ac94f2ced8de5a777e03b8c22110b8ad90d2 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 27 Mar 2022 20:09:50 -0400 Subject: [PATCH 3/4] fix doc bug, put torx_mask first --- color.scad | 2 +- screw_drive.scad | 133 ++++++++++++++++++++++++----------------------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/color.scad b/color.scad index 3bb19f4..1d9b1dc 100644 --- a/color.scad +++ b/color.scad @@ -73,7 +73,7 @@ module color_this(c="default") // Description: // Iterates the list, displaying children in different colors for each list item. The color // is set using the color() module, so this module is not compatible with {{recolor()}} or -// {{color_this}}. This is useful for debugging regions or lists of paths. +// {{color_this()}}. This is useful for debugging regions or lists of paths. // Arguments: // list = The list of items to iterate through. // stride = Consecutive colors stride around the color wheel divided into this many parts. diff --git a/screw_drive.scad b/screw_drive.scad index 179d46c..5598bc2 100644 --- a/screw_drive.scad +++ b/screw_drive.scad @@ -126,6 +126,74 @@ function phillips_diam(size, depth) = // Section: Torx Drive + + +// Module: torx_mask() +// Usage: +// torx_mask(size, l, [center]); +// Description: Creates a torx bit tip. +// Arguments: +// size = Torx size. +// l = Length of bit. +// center = If true, centers bit vertically. +// --- +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +// Examples: +// torx_mask(size=30, l=10, $fa=1, $fs=1); +module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { + anchor = get_anchor(anchor, center, BOT, BOT); + od = torx_outer_diam(size); + attachable(anchor,spin,orient, d=od, l=l) { + linear_extrude(height=l, convexity=4, center=true) { + torx_mask2d(size); + } + children(); + } +} + + + +// Module: torx_mask2d() +// Usage: +// torx_mask2d(size); +// Description: Creates a torx bit 2D profile. +// Arguments: +// size = Torx size. +// Example(2D): +// torx_mask2d(size=30, $fa=1, $fs=1); +module torx_mask2d(size) { + od = torx_outer_diam(size); + id = torx_inner_diam(size); + tip = torx_tip_radius(size); + rounding = torx_rounding_radius(size); + base = od - 2*tip; + $fn = quantup(segs(od/2),12); + difference() { + union() { + circle(d=base); + zrot_copies(n=2) { + hull() { + zrot_copies(n=3) { + translate([base/2,0,0]) { + circle(r=tip, $fn=$fn/2); + } + } + } + } + } + zrot_copies(n=6) { + zrot(180/6) { + translate([id/2+rounding,0,0]) { + circle(r=rounding); + } + } + } + } +} + + // Function: torx_outer_diam() // Usage: // diam = torx_outer_diam(size); @@ -257,71 +325,6 @@ function torx_rounding_radius(size) = lookup(size, [ -// Module: torx_mask2d() -// Usage: -// torx_mask2d(size); -// Description: Creates a torx bit 2D profile. -// Arguments: -// size = Torx size. -// Example(2D): -// torx_mask2d(size=30, $fa=1, $fs=1); -module torx_mask2d(size) { - od = torx_outer_diam(size); - id = torx_inner_diam(size); - tip = torx_tip_radius(size); - rounding = torx_rounding_radius(size); - base = od - 2*tip; - $fn = quantup(segs(od/2),12); - difference() { - union() { - circle(d=base); - zrot_copies(n=2) { - hull() { - zrot_copies(n=3) { - translate([base/2,0,0]) { - circle(r=tip, $fn=$fn/2); - } - } - } - } - } - zrot_copies(n=6) { - zrot(180/6) { - translate([id/2+rounding,0,0]) { - circle(r=rounding); - } - } - } - } -} - - - -// Module: torx_mask() -// Usage: -// torx_mask(size, l, [center]); -// Description: Creates a torx bit tip. -// Arguments: -// size = Torx size. -// l = Length of bit. -// center = If true, centers bit vertically. -// --- -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` -// Examples: -// torx_mask(size=30, l=10, $fa=1, $fs=1); -module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { - anchor = get_anchor(anchor, center, BOT, BOT); - od = torx_outer_diam(size); - attachable(anchor,spin,orient, d=od, l=l) { - linear_extrude(height=l, convexity=4, center=true) { - torx_mask2d(size); - } - children(); - } -} - // Section: Robertson/Square Drives From b9ae9e9a8b320d961ba3e4089ddb6ab1127d6ea9 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 27 Mar 2022 23:06:42 -0400 Subject: [PATCH 4/4] various file reorgs --- color.scad | 30 +- gears.scad | 1534 ++++++++++++++++++----------------- lists.scad | 25 +- math.scad | 864 ++++++++------------ screw_drive.scad | 59 +- tests/README.txt | 3 + tests/test_mutators.scad | 12 +- tests/test_screw_drive.scad | 52 +- tutorials/Mutators.md | 8 +- utility.scad | 199 +++++ 10 files changed, 1403 insertions(+), 1383 deletions(-) create mode 100644 tests/README.txt diff --git a/color.scad b/color.scad index 1d9b1dc..28d46a1 100644 --- a/color.scad +++ b/color.scad @@ -98,17 +98,17 @@ module rainbow(list, stride=1, maxhues, shuffle=false, seed) hues = shuffle ? shuffle(huelist, seed=seed) : huelist; for($idx=idx(list)) { $item = list[$idx]; - HSV(h=hues[$idx]) children(); + hsv(h=hues[$idx]) children(); } } // Section: Colorspace Conversion -// Function&Module: HSL() +// Function&Module: hsl() // Usage: -// HSL(h,[s],[l],[a]) ... -// rgb = HSL(h,[s],[l],[a]); +// hsl(h,[s],[l],[a]) ... +// rgb = hsl(h,[s],[l],[a]); // Description: // When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. If you supply // the `a` value then you'll get a length 4 list [R,G,B,A]. @@ -119,11 +119,11 @@ module rainbow(list, stride=1, maxhues, shuffle=false, seed) // l = The lightness, between 0 and 1. 0 = black, 0.5 = bright colors, 1 = white. Default: 0.5 // a = Specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 // Example: -// HSL(h=120,s=1,l=0.5) sphere(d=60); +// hsl(h=120,s=1,l=0.5) sphere(d=60); // Example: -// rgb = HSL(h=270,s=0.75,l=0.6); +// rgb = hsl(h=270,s=0.75,l=0.6); // color(rgb) cube(60, center=true); -function HSL(h,s=1,l=0.5,a) = +function hsl(h,s=1,l=0.5,a) = let( h=posmod(h,360) ) [ @@ -133,13 +133,13 @@ function HSL(h,s=1,l=0.5,a) = if (is_def(a)) a ]; -module HSL(h,s=1,l=0.5,a=1) color(HSL(h,s,l),a) children(); +module hsl(h,s=1,l=0.5,a=1) color(hsl(h,s,l),a) children(); -// Function&Module: HSV() +// Function&Module: hsv() // Usage: -// HSV(h,[s],[v],[a]) ... -// rgb = HSV(h,[s],[v],[a]); +// hsv(h,[s],[v],[a]) ... +// rgb = hsv(h,[s],[v],[a]); // Description: // When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and value `v` from the HSV colorspace. If you supply // the `a` value then you'll get a length 4 list [R,G,B,A]. @@ -150,11 +150,11 @@ module HSL(h,s=1,l=0.5,a=1) color(HSL(h,s,l),a) children(); // v = The value, between 0 and 1. 0 = darkest black, 1 = bright. Default: 1 // a = Specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1 // Example: -// HSV(h=120,s=1,v=1) sphere(d=60); +// hsv(h=120,s=1,v=1) sphere(d=60); // Example: -// rgb = HSV(h=270,s=0.75,v=0.9); +// rgb = hsv(h=270,s=0.75,v=0.9); // color(rgb) cube(60, center=true); -function HSV(h,s=1,v=1,a) = +function hsv(h,s=1,v=1,a) = assert(s>=0 && s<=1) assert(v>=0 && v<=1) assert(is_undef(a) || a>=0 && a<=1) @@ -175,7 +175,7 @@ function HSV(h,s=1,v=1,a) = is_def(a) ? point4d(add_scalar(rgbprime,m),a) : add_scalar(rgbprime,m); -module HSV(h,s=1,v=1,a=1) color(HSV(h,s,v),a) children(); +module hsv(h,s=1,v=1,a=1) color(hsv(h,s,v),a) children(); diff --git a/gears.scad b/gears.scad index eff47cd..4723026 100644 --- a/gears.scad +++ b/gears.scad @@ -24,626 +24,7 @@ // type of gear its name. -// Section: Functions -// These functions let the user find the derived dimensions of the gear. -// A gear fits within a circle of radius outer_radius, and two gears should have -// their centers separated by the sum of their pitch_radius. - - -// Function: circular_pitch() -// Usage: -// circp = circular_pitch(pitch|mod); -// Topics: Gears -// Description: -// Get tooth density expressed as "circular pitch". -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// mod = The metric module/modulus of the gear. -// Example: -// circp = circular_pitch(pitch=5); -// circp = circular_pitch(mod=2); -function circular_pitch(pitch=5, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - pitch; - - -// Function: diametral_pitch() -// Usage: -// dp = diametral_pitch(pitch|mod); -// Topics: Gears -// Description: -// Get tooth density expressed as "diametral pitch". -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// mod = The metric module/modulus of the gear. -// Example: -// dp = diametral_pitch(pitch=5); -// dp = diametral_pitch(mod=2); -function diametral_pitch(pitch=5, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - PI / pitch; - - -// Function: pitch_value() -// Usage: -// pitch = pitch_value(mod); -// Topics: Gears -// Description: -// Get circular pitch in mm from module/modulus. The circular pitch of a gear is the number of -// millimeters per tooth around the pitch radius circle. -// Arguments: -// mod = The module/modulus of the gear. -function pitch_value(mod) = mod * PI; - - -// Function: module_value() -// Usage: -// mod = module_value(pitch); -// Topics: Gears -// Description: -// Get tooth density expressed as "module" or "modulus" in millimeters. The module is the pitch -// diameter of the gear divided by the number of teeth on it. For example, a gear with a pitch -// diameter of 40mm, with 20 teeth on it will have a modulus of 2. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -function module_value(pitch=5) = pitch / PI; - - -// Function: adendum() -// Usage: -// ad = adendum(pitch|mod); -// Topics: Gears -// Description: -// The height of the top of a gear tooth above the pitch radius circle. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// mod = The metric module/modulus of the gear. -// Example: -// ad = adendum(pitch=5); -// ad = adendum(mod=2); -// Example(2D): -// pitch = 5; teeth = 17; -// pr = pitch_radius(pitch=pitch, teeth=teeth); -// adn = adendum(pitch=5); -// #spur_gear2d(pitch=pitch, teeth=teeth); -// color("black") { -// stroke(circle(r=pr),width=0.1,closed=true); -// stroke(circle(r=pr+adn),width=0.1,closed=true); -// } -function adendum(pitch=5, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - module_value(pitch) * 1.0; - - -// Function: dedendum() -// Usage: -// ddn = dedendum(pitch|mod, [clearance]); -// Topics: Gears -// Description: -// The depth of the gear tooth valley, below the pitch radius. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// clearance = If given, sets the clearance between meshing teeth. -// mod = The metric module/modulus of the gear. -// Example: -// ddn = dedendum(pitch=5); -// ddn = dedendum(mod=2); -// Example(2D): -// pitch = 5; teeth = 17; -// pr = pitch_radius(pitch=pitch, teeth=teeth); -// ddn = dedendum(pitch=5); -// #spur_gear2d(pitch=pitch, teeth=teeth); -// color("black") { -// stroke(circle(r=pr),width=0.1,closed=true); -// stroke(circle(r=pr-ddn),width=0.1,closed=true); -// } -function dedendum(pitch=5, clearance, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - is_undef(clearance)? (1.25 * module_value(pitch)) : - (module_value(pitch) + clearance); - - -// Function: pitch_radius() -// Usage: -// pr = pitch_radius(pitch|mod, teeth); -// Topics: Gears -// Description: -// Calculates the pitch radius for the gear. Two mated gears will have their centers spaced apart -// by the sum of the two gear's pitch radii. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = The number of teeth on the gear. -// mod = The metric module/modulus of the gear. -// Example: -// pr = pitch_radius(pitch=5, teeth=11); -// pr = pitch_radius(mod=2, teeth=20); -// Example(2D): -// pr = pitch_radius(pitch=5, teeth=11); -// #spur_gear2d(pitch=5, teeth=11); -// color("black") -// stroke(circle(r=pr),width=0.1,closed=true); -function pitch_radius(pitch=5, teeth=11, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - pitch * teeth / PI / 2; - - -// Function: outer_radius() -// Usage: -// or = outer_radius(pitch|mod, teeth, [clearance], [interior]); -// Topics: Gears -// Description: -// Calculates the outer radius for the gear. The gear fits entirely within a cylinder of this radius. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = The number of teeth on the gear. -// clearance = If given, sets the clearance between meshing teeth. -// interior = If true, calculate for an interior gear. -// mod = The metric module/modulus of the gear. -// Example: -// or = outer_radius(pitch=5, teeth=20); -// or = outer_radius(mod=2, teeth=16); -// Example(2D): -// pr = outer_radius(pitch=5, teeth=11); -// #spur_gear2d(pitch=5, teeth=11); -// color("black") -// stroke(circle(r=pr),width=0.1,closed=true); -function outer_radius(pitch=5, teeth=11, clearance, interior=false, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - pitch_radius(pitch, teeth) + - (interior? dedendum(pitch, clearance) : adendum(pitch)); - - -// Function: root_radius() -// Usage: -// rr = root_radius(pitch|mod, teeth, [clearance], [interior]); -// Topics: Gears -// Description: -// Calculates the root radius for the gear, at the base of the dedendum. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = The number of teeth on the gear. -// clearance = If given, sets the clearance between meshing teeth. -// interior = If true, calculate for an interior gear. -// mod = The metric module/modulus of the gear. -// Example: -// rr = root_radius(pitch=5, teeth=11); -// rr = root_radius(mod=2, teeth=16); -// Example(2D): -// pr = root_radius(pitch=5, teeth=11); -// #spur_gear2d(pitch=5, teeth=11); -// color("black") -// stroke(circle(r=pr),width=0.1,closed=true); -function root_radius(pitch=5, teeth=11, clearance, interior=false, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - pitch_radius(pitch, teeth) - - (interior? adendum(pitch) : dedendum(pitch, clearance)); - - -// Function: base_radius() -// Usage: -// br = base_radius(pitch|mod, teeth, [pressure_angle]); -// Topics: Gears -// Description: -// Get the base circle for involute teeth, at the base of the teeth. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = The number of teeth on the gear. -// pressure_angle = Pressure angle in degrees. Controls how straight or bulged the tooth sides are. -// mod = The metric module/modulus of the gear. -// Example: -// br = base_radius(pitch=5, teeth=20, pressure_angle=20); -// br = base_radius(mod=2, teeth=18, pressure_angle=20); -// Example(2D): -// pr = base_radius(pitch=5, teeth=11); -// #spur_gear2d(pitch=5, teeth=11); -// color("black") -// stroke(circle(r=pr),width=0.1,closed=true); -function base_radius(pitch=5, teeth=11, pressure_angle=28, mod) = - let( pitch = is_undef(mod) ? pitch : pitch_value(mod) ) - pitch_radius(pitch, teeth) * cos(pressure_angle); - - -// Function: bevel_pitch_angle() -// Usage: -// ang = bevel_pitch_angle(teeth, mate_teeth, [drive_angle]); -// Topics: Gears -// See Also: bevel_gear() -// Description: -// Returns the correct pitch angle for a bevel gear with a given number of tooth, that is -// matched to another bevel gear with a (possibly different) number of teeth. -// Arguments: -// teeth = Number of teeth that this gear has. -// mate_teeth = Number of teeth that the matching gear has. -// drive_angle = Angle between the drive shafts of each gear. Default: 90º. -// Example: -// ang = bevel_pitch_angle(teeth=18, mate_teeth=30); -// Example(2D): -// t1 = 13; t2 = 19; pitch=5; -// pang = bevel_pitch_angle(teeth=t1, mate_teeth=t2, drive_angle=90); -// color("black") { -// zrot_copies([0,pang]) -// stroke([[0,0,0], [0,-20,0]],width=0.2); -// stroke(arc(r=3, angle=[270,270+pang]),width=0.2); -// } -// #bevel_gear( -// pitch=5, teeth=t1, mate_teeth=t2, -// spiral_angle=0, cutter_radius=1000, -// slices=12, anchor="apex", orient=BACK -// ); -function bevel_pitch_angle(teeth, mate_teeth, drive_angle=90) = - atan(sin(drive_angle)/((mate_teeth/teeth)+cos(drive_angle))); - - -// Function: worm_gear_thickness() -// Usage: -// thick = worm_gear_thickness(pitch|mod, teeth, worm_diam, [worm_arc], [crowning], [clearance]); -// Topics: Gears -// See Also: worm(), worm_gear() -// Description: -// Calculate the thickness of the worm gear. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. Default: 5 -// teeth = Total number of teeth along the rack. Default: 30 -// worm_diam = The pitch diameter of the worm gear to match to. Default: 30 -// worm_arc = The arc of the worm to mate with, in degrees. Default: 60 degrees -// crowning = The amount to oversize the virtual hobbing cutter used to make the teeth, to add a slight crowning to the teeth to make them fir the work easier. Default: 1 -// clearance = Clearance gap at the bottom of the inter-tooth valleys. -// mod = The metric module/modulus of the gear. -// Example: -// thick = worm_gear_thickness(pitch=5, teeth=36, worm_diam=30); -// thick = worm_gear_thickness(mod=2, teeth=28, worm_diam=25); -// Example(2D): -// pitch = 5; teeth=17; -// worm_diam = 30; starts=2; -// y = worm_gear_thickness(pitch=pitch, teeth=teeth, worm_diam=worm_diam); -// #worm_gear( -// pitch=pitch, teeth=teeth, -// worm_diam=worm_diam, -// worm_starts=starts, -// orient=BACK -// ); -// color("black") { -// ycopies(y) stroke([[-25,0],[25,0]], width=0.5); -// stroke([[-20,-y/2],[-20,y/2]],width=0.5,endcaps="arrow"); -// } -function worm_gear_thickness(pitch=5, teeth=30, worm_diam=30, worm_arc=60, crowning=1, clearance, mod) = - let( - pitch = is_undef(mod) ? pitch : pitch_value(mod), - r = worm_diam/2 + crowning, - pitch_thick = r * sin(worm_arc/2) * 2, - pr = pitch_radius(pitch, teeth), - rr = root_radius(pitch, teeth, clearance, false), - pitchoff = (pr-rr) * sin(worm_arc/2), - thickness = pitch_thick + 2*pitchoff - ) thickness; - - -function _gear_polar(r,t) = r*[sin(t),cos(t)]; -function _gear_iang(r1,r2) = sqrt((r2/r1)*(r2/r1) - 1)/PI*180 - acos(r1/r2); //unwind a string this many degrees to go from radius r1 to radius r2 -function _gear_q6(b,s,t,d) = _gear_polar(d,s*(_gear_iang(b,d)+t)); //point at radius d on the involute curve -function _gear_q7(f,r,b,r2,t,s) = _gear_q6(b,s,t,(1-f)*max(b,r)+f*r2); //radius a fraction f up the curved side of the tooth - - -// Section: 2D Profiles - - -// Function&Module: gear_tooth_profile() -// Usage: As Module -// gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]); -// Usage: As Function -// path = gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]); -// Topics: Gears -// See Also: spur_gear2d() -// Description: -// When called as a function, returns the 2D profile path for an individual gear tooth. -// When called as a module, creates the 2D profile shape for an individual gear tooth. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = Total number of teeth on the spur gear that this is a tooth for. -// pressure_angle = Pressure Angle. Controls how straight or bulged the tooth sides are. In degrees. -// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) -// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// interior = If true, create a mask for difference()ing from something else. -// valleys = If true, add the valley bottoms on either side of the tooth. Default: true -// center = If true, centers the pitch circle of the tooth profile at the origin. Default: false. -// mod = The metric module/modulus of the gear. -// Example(2D): -// gear_tooth_profile(pitch=5, teeth=20, pressure_angle=20); -// Example(2D): Metric Gear Tooth -// gear_tooth_profile(mod=2, teeth=20, pressure_angle=20); -// Example(2D): -// gear_tooth_profile( -// pitch=5, teeth=20, pressure_angle=20, valleys=false -// ); -// Example(2D): As a function -// path = gear_tooth_profile( -// pitch=5, teeth=20, pressure_angle=20, valleys=false -// ); -// stroke(path, width=0.1); -function gear_tooth_profile( - pitch = 3, - teeth = 11, - pressure_angle = 28, - clearance = undef, - backlash = 0.0, - interior = false, - valleys = true, - center = false, - mod -) = let( - pitch = is_undef(mod) ? pitch : pitch_value(mod), - p = pitch_radius(pitch, teeth), - c = outer_radius(pitch, teeth, clearance, interior), - r = root_radius(pitch, teeth, clearance, interior), - b = base_radius(pitch, teeth, pressure_angle), - t = pitch/2-backlash/2, //tooth thickness at pitch circle - k = -_gear_iang(b, p) - t/2/p/PI*180, //angle to where involute meets base circle on each side of tooth - kk = r0? [[0,0]] : [] - ) -) reorient(anchor,spin, two_d=true, r=pr, p=pts); - - -module spur_gear2d( - pitch = 3, - teeth = 11, - hide = 0, - pressure_angle = 28, - clearance = undef, - backlash = 0.0, - interior = false, - mod, - anchor = CENTER, - spin = 0 -) { - pitch = is_undef(mod) ? pitch : pitch_value(mod); - path = spur_gear2d( - pitch = pitch, - teeth = teeth, - hide = hide, - pressure_angle = pressure_angle, - clearance = clearance, - backlash = backlash, - interior = interior - ); - pr = pitch_radius(pitch=pitch, teeth=teeth); - attachable(anchor,spin, two_d=true, r=pr) { - polygon(path); - children(); - } -} - - -// Function&Module: rack2d() -// Usage: As a Function -// path = rack2d(pitch|mod, teeth, height, [pressure_angle], [backlash]); -// Usage: As a Module -// rack2d(pitch|mod, teeth, height, [pressure_angle], [backlash]); -// Topics: Gears -// See Also: spur_gear2d() -// Description: -// This is used to create a 2D rack, which is a linear bar with teeth that a gear can roll along. -// A rack can mesh with any gear that has the same `pitch` and `pressure_angle`. -// When called as a function, returns a 2D path for the outline of the rack. -// When called as a module, creates a 2D rack shape. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. -// teeth = Total number of teeth along the rack -// height = Height of rack in mm, from tooth top to back of rack. -// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. -// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// mod = The metric module/modulus of the gear. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: -// "adendum" = At the tips of the teeth, at the center of rack. -// "adendum-left" = At the tips of the teeth, at the left end of the rack. -// "adendum-right" = At the tips of the teeth, at the right end of the rack. -// "dedendum" = At the height of the teeth, at the center of rack. -// "dedendum-left" = At the height of the teeth, at the left end of the rack. -// "dedendum-right" = At the height of the teeth, at the right end of the rack. -// Example(2D): -// rack2d(pitch=5, teeth=10, height=10, pressure_angle=20); -// Example(2D): Called as a Function -// path = rack2d(pitch=8, teeth=8, height=10, pressure_angle=28); -// polygon(path); -function rack2d( - pitch = 5, - teeth = 20, - height = 10, - pressure_angle = 28, - backlash = 0.0, - clearance = undef, - mod, - anchor = CENTER, - spin = 0 -) = - let( - pitch = is_undef(mod) ? pitch : pitch_value(mod), - a = adendum(pitch), - d = dedendum(pitch, clearance) - ) - assert(a+d < height) - let( - xa = a * sin(pressure_angle), - xd = d * sin(pressure_angle), - l = teeth * pitch, - anchors = [ - named_anchor("adendum", [ 0, a,0], BACK), - named_anchor("adendum-left", [-l/2, a,0], LEFT), - named_anchor("adendum-right", [ l/2, a,0], RIGHT), - named_anchor("dedendum", [ 0,-d,0], BACK), - named_anchor("dedendum-left", [-l/2,-d,0], LEFT), - named_anchor("dedendum-right", [ l/2,-d,0], RIGHT), - ], - path = [ - [-(teeth-1)/2 * pitch + -1/2 * pitch, a-height], - [-(teeth-1)/2 * pitch + -1/2 * pitch, -d], - for (i = [0:1:teeth-1]) let( - off = (i-(teeth-1)/2) * pitch - ) each [ - [off + -1/4 * pitch + backlash - xd, -d], - [off + -1/4 * pitch + backlash + xa, a], - [off + 1/4 * pitch - backlash - xa, a], - [off + 1/4 * pitch - backlash + xd, -d], - ], - [ (teeth-1)/2 * pitch + 1/2 * pitch, -d], - [ (teeth-1)/2 * pitch + 1/2 * pitch, a-height], - ] - ) reorient(anchor,spin, two_d=true, size=[l,2*abs(a-height)], anchors=anchors, p=path); - - -module rack2d( - pitch = 5, - teeth = 20, - height = 10, - pressure_angle = 28, - backlash = 0.0, - clearance = undef, - mod, - anchor = CENTER, - spin = 0 -) { - pitch = is_undef(mod) ? pitch : pitch_value(mod); - a = adendum(pitch); - d = dedendum(pitch, clearance); - l = teeth * pitch; - anchors = [ - named_anchor("adendum", [ 0, a,0], BACK), - named_anchor("adendum-left", [-l/2, a,0], LEFT), - named_anchor("adendum-right", [ l/2, a,0], RIGHT), - named_anchor("dedendum", [ 0,-d,0], BACK), - named_anchor("dedendum-left", [-l/2,-d,0], LEFT), - named_anchor("dedendum-right", [ l/2,-d,0], RIGHT), - ]; - path = rack2d( - pitch = pitch, - teeth = teeth, - height = height, - pressure_angle = pressure_angle, - backlash = backlash, - clearance = clearance - ); - attachable(anchor,spin, two_d=true, size=[l, 2*abs(a-height)], anchors=anchors) { - polygon(path); - children(); - } -} - - - -// Section: 3D Gears and Racks - +// Section: Gears // Function&Module: spur_gear() // Usage: As a Module @@ -810,6 +191,370 @@ module spur_gear( } +// Function&Module: spur_gear2d() +// Usage: As Module +// spur_gear2d(pitch|mod, teeth, [hide], [pressure_angle], [clearance], [backlash], [interior]); +// Usage: As Function +// poly = spur_gear2d(pitch|mod, teeth, [hide], [pressure_angle], [clearance], [backlash], [interior]); +// Topics: Gears +// See Also: spur_gear() +// Description: +// When called as a module, creates a 2D involute spur gear. When called as a function, returns a +// 2D path for the perimeter of a 2D involute spur gear. Normally, you should just specify the +// first 2 parameters `pitch` and `teeth`, and let the rest be default values. +// Meshing gears must match in `pitch`, `pressure_angle`, and `helical`, and be separated by +// the sum of their pitch radii, which can be found with `pitch_radius()`. +// Arguments: +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth around the spur gear. +// hide = Number of teeth to delete to make this only a fraction of a circle +// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// interior = If true, create a mask for difference()ing from something else. +// mod = The metric module/modulus of the gear. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// Example(2D): Typical Gear Shape +// spur_gear2d(pitch=5, teeth=20); +// Example(2D): Metric Gear +// spur_gear2d(mod=2, teeth=20); +// Example(2D): Lower Pressure Angle +// spur_gear2d(pitch=5, teeth=20, pressure_angle=20); +// Example(2D): Partial Gear +// spur_gear2d(pitch=5, teeth=20, hide=15, pressure_angle=20); +// Example(2D): Called as a Function +// path = spur_gear2d(pitch=8, teeth=16); +// polygon(path); +function spur_gear2d( + pitch = 3, + teeth = 11, + hide = 0, + pressure_angle = 28, + clearance = undef, + backlash = 0.0, + interior = false, + mod, + anchor = CENTER, + spin = 0 +) = let( + pitch = is_undef(mod) ? pitch : pitch_value(mod), + pr = pitch_radius(pitch=pitch, teeth=teeth), + tooth_profile = gear_tooth_profile( + pitch = pitch, + teeth = teeth, + pressure_angle = pressure_angle, + clearance = clearance, + backlash = backlash, + interior = interior, + valleys = false + ), + pts = concat( + [for (tooth = [0:1:teeth-hide-1]) + each rot(tooth*360/teeth, p=tooth_profile) + ], + hide>0? [[0,0]] : [] + ) +) reorient(anchor,spin, two_d=true, r=pr, p=pts); + + +module spur_gear2d( + pitch = 3, + teeth = 11, + hide = 0, + pressure_angle = 28, + clearance = undef, + backlash = 0.0, + interior = false, + mod, + anchor = CENTER, + spin = 0 +) { + pitch = is_undef(mod) ? pitch : pitch_value(mod); + path = spur_gear2d( + pitch = pitch, + teeth = teeth, + hide = hide, + pressure_angle = pressure_angle, + clearance = clearance, + backlash = backlash, + interior = interior + ); + pr = pitch_radius(pitch=pitch, teeth=teeth); + attachable(anchor,spin, two_d=true, r=pr) { + polygon(path); + children(); + } +} + + + +// Function&Module: rack() +// Usage: As a Module +// rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]); +// rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash]=); +// Usage: As a Function +// vnf = rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]); +// vnf = rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash=]); +// Topics: Gears +// See Also: spur_gear() +// Description: +// This is used to create a 3D rack, which is a linear bar with teeth that a gear can roll along. +// A rack can mesh with any gear that has the same `pitch` and `pressure_angle`. +// When called as a function, returns a 3D [VNF](vnf.scad) for the rack. +// When called as a module, creates a 3D rack shape. +// Arguments: +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. Default: 5 +// teeth = Total number of teeth along the rack. Default: 20 +// thickness = Thickness of rack in mm (affects each tooth). Default: 5 +// height = Height of rack in mm, from tooth top to back of rack. Default: 10 +// --- +// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 28 +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle. Default: 0 +// clearance = Clearance gap at the bottom of the inter-tooth valleys. +// helical = The angle of the rack teeth away from perpendicular to the rack length. Used to match helical spur gear pinions. Default: 0 +// mod = The metric module/modulus of the gear. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` +// Extra Anchors: +// "adendum" = At the tips of the teeth, at the center of rack. +// "adendum-left" = At the tips of the teeth, at the left end of the rack. +// "adendum-right" = At the tips of the teeth, at the right end of the rack. +// "adendum-back" = At the tips of the teeth, at the back of the rack. +// "adendum-front" = At the tips of the teeth, at the front of the rack. +// "dedendum" = At the base of the teeth, at the center of rack. +// "dedendum-left" = At the base of the teeth, at the left end of the rack. +// "dedendum-right" = At the base of the teeth, at the right end of the rack. +// "dedendum-back" = At the base of the teeth, at the back of the rack. +// "dedendum-front" = At the base of the teeth, at the front of the rack. +// Example(VPR=[60,0,325],VPD=130): +// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20); +// Example: Rack for Helical Gear +// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=30); +// Example: Alternate Helical Gear +// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=-30); +// Example: Metric Rack +// rack(mod=2, teeth=10, thickness=5, height=5, pressure_angle=20); +// Example(Anim,VPT=[0,0,12],VPD=100,Frames=6): Rack and Pinion +// teeth1 = 16; teeth2 = 16; +// pitch = 5; thick = 5; helical = 30; +// pr = pitch_radius(pitch=pitch, teeth=teeth2); +// right(pr*2*PI/teeth2*$t) rack(pitch=pitch, teeth=teeth1, thickness=thick, height=5, helical=helical); +// up(pr) yrot(186.5-$t*360/teeth2) +// spur_gear(pitch=pitch, teeth=teeth2, thickness=thick, helical=helical, shaft_diam=5, orient=BACK); +module rack( + pitch = 5, + teeth = 20, + thickness = 5, + height = 10, + pressure_angle = 28, + backlash = 0.0, + clearance, + helical=0, + mod, + anchor = CENTER, + spin = 0, + orient = UP +) { + pitch = is_undef(mod) ? pitch : pitch_value(mod); + a = adendum(pitch); + d = dedendum(pitch, clearance); + l = teeth * pitch; + anchors = [ + named_anchor("adendum", [0,0,a], BACK), + named_anchor("adendum-left", [-l/2,0,a], LEFT), + named_anchor("adendum-right", [ l/2,0,a], RIGHT), + named_anchor("adendum-front", [0,-thickness/2,a], DOWN), + named_anchor("adendum-back", [0, thickness/2,a], UP), + named_anchor("dedendum", [0,0,-d], BACK), + named_anchor("dedendum-left", [-l/2,0,-d], LEFT), + named_anchor("dedendum-right", [ l/2,0,-d], RIGHT), + named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN), + named_anchor("dedendum-back", [0, thickness/2,-d], UP), + ]; + attachable(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors) { + skew(sxy=tan(helical)) xrot(90) { + linear_extrude(height=thickness, center=true, convexity=teeth*2) { + rack2d( + pitch = pitch, + teeth = teeth, + height = height, + pressure_angle = pressure_angle, + backlash = backlash, + clearance = clearance + ); + } + } + children(); + } +} + + +function rack( + pitch = 5, + teeth = 20, + thickness = 5, + height = 10, + pressure_angle = 28, + backlash = 0.0, + clearance, + helical=0, + mod, + anchor = CENTER, + spin = 0, + orient = UP +) = + let( + pitch = is_undef(mod) ? pitch : pitch_value(mod), + a = adendum(pitch), + d = dedendum(pitch, clearance), + l = teeth * pitch, + anchors = [ + named_anchor("adendum", [0,0,a], BACK), + named_anchor("adendum-left", [-l/2,0,a], LEFT), + named_anchor("adendum-right", [ l/2,0,a], RIGHT), + named_anchor("adendum-front", [0,-thickness/2,a], DOWN), + named_anchor("adendum-back", [0, thickness/2,a], UP), + named_anchor("dedendum", [0,0,-d], BACK), + named_anchor("dedendum-left", [-l/2,0,-d], LEFT), + named_anchor("dedendum-right", [ l/2,0,-d], RIGHT), + named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN), + named_anchor("dedendum-back", [0, thickness/2,-d], UP), + ], + path = rack2d( + pitch = pitch, + teeth = teeth, + height = height, + pressure_angle = pressure_angle, + backlash = backlash, + clearance = clearance + ), + vnf = linear_sweep(path, height=thickness, anchor="origin", orient=FWD), + out = helical==0? vnf : skew(sxy=tan(helical), p=vnf) + ) reorient(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors, p=out); + + + + +// Function&Module: rack2d() +// Usage: As a Function +// path = rack2d(pitch|mod, teeth, height, [pressure_angle], [backlash]); +// Usage: As a Module +// rack2d(pitch|mod, teeth, height, [pressure_angle], [backlash]); +// Topics: Gears +// See Also: spur_gear2d() +// Description: +// This is used to create a 2D rack, which is a linear bar with teeth that a gear can roll along. +// A rack can mesh with any gear that has the same `pitch` and `pressure_angle`. +// When called as a function, returns a 2D path for the outline of the rack. +// When called as a module, creates a 2D rack shape. +// Arguments: +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth along the rack +// height = Height of rack in mm, from tooth top to back of rack. +// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// mod = The metric module/modulus of the gear. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// Extra Anchors: +// "adendum" = At the tips of the teeth, at the center of rack. +// "adendum-left" = At the tips of the teeth, at the left end of the rack. +// "adendum-right" = At the tips of the teeth, at the right end of the rack. +// "dedendum" = At the height of the teeth, at the center of rack. +// "dedendum-left" = At the height of the teeth, at the left end of the rack. +// "dedendum-right" = At the height of the teeth, at the right end of the rack. +// Example(2D): +// rack2d(pitch=5, teeth=10, height=10, pressure_angle=20); +// Example(2D): Called as a Function +// path = rack2d(pitch=8, teeth=8, height=10, pressure_angle=28); +// polygon(path); +function rack2d( + pitch = 5, + teeth = 20, + height = 10, + pressure_angle = 28, + backlash = 0.0, + clearance = undef, + mod, + anchor = CENTER, + spin = 0 +) = + let( + pitch = is_undef(mod) ? pitch : pitch_value(mod), + a = adendum(pitch), + d = dedendum(pitch, clearance) + ) + assert(a+d < height) + let( + xa = a * sin(pressure_angle), + xd = d * sin(pressure_angle), + l = teeth * pitch, + anchors = [ + named_anchor("adendum", [ 0, a,0], BACK), + named_anchor("adendum-left", [-l/2, a,0], LEFT), + named_anchor("adendum-right", [ l/2, a,0], RIGHT), + named_anchor("dedendum", [ 0,-d,0], BACK), + named_anchor("dedendum-left", [-l/2,-d,0], LEFT), + named_anchor("dedendum-right", [ l/2,-d,0], RIGHT), + ], + path = [ + [-(teeth-1)/2 * pitch + -1/2 * pitch, a-height], + [-(teeth-1)/2 * pitch + -1/2 * pitch, -d], + for (i = [0:1:teeth-1]) let( + off = (i-(teeth-1)/2) * pitch + ) each [ + [off + -1/4 * pitch + backlash - xd, -d], + [off + -1/4 * pitch + backlash + xa, a], + [off + 1/4 * pitch - backlash - xa, a], + [off + 1/4 * pitch - backlash + xd, -d], + ], + [ (teeth-1)/2 * pitch + 1/2 * pitch, -d], + [ (teeth-1)/2 * pitch + 1/2 * pitch, a-height], + ] + ) reorient(anchor,spin, two_d=true, size=[l,2*abs(a-height)], anchors=anchors, p=path); + + +module rack2d( + pitch = 5, + teeth = 20, + height = 10, + pressure_angle = 28, + backlash = 0.0, + clearance = undef, + mod, + anchor = CENTER, + spin = 0 +) { + pitch = is_undef(mod) ? pitch : pitch_value(mod); + a = adendum(pitch); + d = dedendum(pitch, clearance); + l = teeth * pitch; + anchors = [ + named_anchor("adendum", [ 0, a,0], BACK), + named_anchor("adendum-left", [-l/2, a,0], LEFT), + named_anchor("adendum-right", [ l/2, a,0], RIGHT), + named_anchor("dedendum", [ 0,-d,0], BACK), + named_anchor("dedendum-left", [-l/2,-d,0], LEFT), + named_anchor("dedendum-right", [ l/2,-d,0], RIGHT), + ]; + path = rack2d( + pitch = pitch, + teeth = teeth, + height = height, + pressure_angle = pressure_angle, + backlash = backlash, + clearance = clearance + ); + attachable(anchor,spin, two_d=true, size=[l, 2*abs(a-height)], anchors=anchors) { + polygon(path); + children(); + } +} + + + // Function&Module: bevel_gear() // Usage: As a Module @@ -1064,150 +809,6 @@ module bevel_gear( } -// Function&Module: rack() -// Usage: As a Module -// rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]); -// rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash]=); -// Usage: As a Function -// vnf = rack(pitch, teeth, thickness, height, [pressure_angle=], [backlash=]); -// vnf = rack(mod=, teeth=, thickness=, height=, [pressure_angle=], [backlash=]); -// Topics: Gears -// See Also: spur_gear() -// Description: -// This is used to create a 3D rack, which is a linear bar with teeth that a gear can roll along. -// A rack can mesh with any gear that has the same `pitch` and `pressure_angle`. -// When called as a function, returns a 3D [VNF](vnf.scad) for the rack. -// When called as a module, creates a 3D rack shape. -// Arguments: -// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. Default: 5 -// teeth = Total number of teeth along the rack. Default: 20 -// thickness = Thickness of rack in mm (affects each tooth). Default: 5 -// height = Height of rack in mm, from tooth top to back of rack. Default: 10 -// --- -// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. Default: 28 -// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle. Default: 0 -// clearance = Clearance gap at the bottom of the inter-tooth valleys. -// helical = The angle of the rack teeth away from perpendicular to the rack length. Used to match helical spur gear pinions. Default: 0 -// mod = The metric module/modulus of the gear. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` -// Extra Anchors: -// "adendum" = At the tips of the teeth, at the center of rack. -// "adendum-left" = At the tips of the teeth, at the left end of the rack. -// "adendum-right" = At the tips of the teeth, at the right end of the rack. -// "adendum-back" = At the tips of the teeth, at the back of the rack. -// "adendum-front" = At the tips of the teeth, at the front of the rack. -// "dedendum" = At the base of the teeth, at the center of rack. -// "dedendum-left" = At the base of the teeth, at the left end of the rack. -// "dedendum-right" = At the base of the teeth, at the right end of the rack. -// "dedendum-back" = At the base of the teeth, at the back of the rack. -// "dedendum-front" = At the base of the teeth, at the front of the rack. -// Example(VPR=[60,0,325],VPD=130): -// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20); -// Example: Rack for Helical Gear -// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=30); -// Example: Alternate Helical Gear -// rack(pitch=5, teeth=10, thickness=5, height=5, pressure_angle=20, helical=-30); -// Example: Metric Rack -// rack(mod=2, teeth=10, thickness=5, height=5, pressure_angle=20); -// Example(Anim,VPT=[0,0,12],VPD=100,Frames=6): Rack and Pinion -// teeth1 = 16; teeth2 = 16; -// pitch = 5; thick = 5; helical = 30; -// pr = pitch_radius(pitch=pitch, teeth=teeth2); -// right(pr*2*PI/teeth2*$t) rack(pitch=pitch, teeth=teeth1, thickness=thick, height=5, helical=helical); -// up(pr) yrot(186.5-$t*360/teeth2) -// spur_gear(pitch=pitch, teeth=teeth2, thickness=thick, helical=helical, shaft_diam=5, orient=BACK); -module rack( - pitch = 5, - teeth = 20, - thickness = 5, - height = 10, - pressure_angle = 28, - backlash = 0.0, - clearance, - helical=0, - mod, - anchor = CENTER, - spin = 0, - orient = UP -) { - pitch = is_undef(mod) ? pitch : pitch_value(mod); - a = adendum(pitch); - d = dedendum(pitch, clearance); - l = teeth * pitch; - anchors = [ - named_anchor("adendum", [0,0,a], BACK), - named_anchor("adendum-left", [-l/2,0,a], LEFT), - named_anchor("adendum-right", [ l/2,0,a], RIGHT), - named_anchor("adendum-front", [0,-thickness/2,a], DOWN), - named_anchor("adendum-back", [0, thickness/2,a], UP), - named_anchor("dedendum", [0,0,-d], BACK), - named_anchor("dedendum-left", [-l/2,0,-d], LEFT), - named_anchor("dedendum-right", [ l/2,0,-d], RIGHT), - named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN), - named_anchor("dedendum-back", [0, thickness/2,-d], UP), - ]; - attachable(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors) { - skew(sxy=tan(helical)) xrot(90) { - linear_extrude(height=thickness, center=true, convexity=teeth*2) { - rack2d( - pitch = pitch, - teeth = teeth, - height = height, - pressure_angle = pressure_angle, - backlash = backlash, - clearance = clearance - ); - } - } - children(); - } -} - - -function rack( - pitch = 5, - teeth = 20, - thickness = 5, - height = 10, - pressure_angle = 28, - backlash = 0.0, - clearance, - helical=0, - mod, - anchor = CENTER, - spin = 0, - orient = UP -) = - let( - pitch = is_undef(mod) ? pitch : pitch_value(mod), - a = adendum(pitch), - d = dedendum(pitch, clearance), - l = teeth * pitch, - anchors = [ - named_anchor("adendum", [0,0,a], BACK), - named_anchor("adendum-left", [-l/2,0,a], LEFT), - named_anchor("adendum-right", [ l/2,0,a], RIGHT), - named_anchor("adendum-front", [0,-thickness/2,a], DOWN), - named_anchor("adendum-back", [0, thickness/2,a], UP), - named_anchor("dedendum", [0,0,-d], BACK), - named_anchor("dedendum-left", [-l/2,0,-d], LEFT), - named_anchor("dedendum-right", [ l/2,0,-d], RIGHT), - named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN), - named_anchor("dedendum-back", [0, thickness/2,-d], UP), - ], - path = rack2d( - pitch = pitch, - teeth = teeth, - height = height, - pressure_angle = pressure_angle, - backlash = backlash, - clearance = clearance - ), - vnf = linear_sweep(path, height=thickness, anchor="origin", orient=FWD), - out = helical==0? vnf : skew(sxy=tan(helical), p=vnf) - ) reorient(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors, p=out); @@ -1512,4 +1113,409 @@ module worm_gear( } +// Section: 2D Profiles + + +// Function&Module: gear_tooth_profile() +// Usage: As Module +// gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]); +// Usage: As Function +// path = gear_tooth_profile(pitch|mod, teeth, [pressure_angle], [clearance], [backlash], [interior], [valleys]); +// Topics: Gears +// See Also: spur_gear2d() +// Description: +// When called as a function, returns the 2D profile path for an individual gear tooth. +// When called as a module, creates the 2D profile shape for an individual gear tooth. +// Arguments: +// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm. +// teeth = Total number of teeth on the spur gear that this is a tooth for. +// pressure_angle = Pressure Angle. Controls how straight or bulged the tooth sides are. In degrees. +// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// interior = If true, create a mask for difference()ing from something else. +// valleys = If true, add the valley bottoms on either side of the tooth. Default: true +// center = If true, centers the pitch circle of the tooth profile at the origin. Default: false. +// mod = The metric module/modulus of the gear. +// Example(2D): +// gear_tooth_profile(pitch=5, teeth=20, pressure_angle=20); +// Example(2D): Metric Gear Tooth +// gear_tooth_profile(mod=2, teeth=20, pressure_angle=20); +// Example(2D): +// gear_tooth_profile( +// pitch=5, teeth=20, pressure_angle=20, valleys=false +// ); +// Example(2D): As a function +// path = gear_tooth_profile( +// pitch=5, teeth=20, pressure_angle=20, valleys=false +// ); +// stroke(path, width=0.1); +function gear_tooth_profile( + pitch = 3, + teeth = 11, + pressure_angle = 28, + clearance = undef, + backlash = 0.0, + interior = false, + valleys = true, + center = false, + mod +) = let( + pitch = is_undef(mod) ? pitch : pitch_value(mod), + p = pitch_radius(pitch, teeth), + c = outer_radius(pitch, teeth, clearance, interior), + r = root_radius(pitch, teeth, clearance, interior), + b = base_radius(pitch, teeth, pressure_angle), + t = pitch/2-backlash/2, //tooth thickness at pitch circle + k = -_gear_iang(b, p) - t/2/p/PI*180, //angle to where involute meets base circle on each side of tooth + kk = r // FileGroup: Math -// FileSummary: General miscellaneous math function. +// FileSummary: Math on lists, special functions, quantization, random numbers, calculus, root finding +// // FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// @@ -28,7 +31,97 @@ NAN = acos(2); -// Section: Simple math +// Section: Interpolation and Counting + + +// Function: count() +// Usage: +// list = count(n, [s], [step], [reverse]); +// Description: +// Creates a list of `n` numbers, starting at `s`, incrementing by `step` each time. +// You can also pass a list for n and then the length of the input list is used. +// Arguments: +// n = The length of the list of numbers to create, or a list to match the length of +// s = The starting value of the list of numbers. +// step = The amount to increment successive numbers in the list. +// reverse = Reverse the list. Default: false. +// See Also: idx() +// Example: +// nl1 = count(5); // Returns: [0,1,2,3,4] +// nl2 = count(5,3); // Returns: [3,4,5,6,7] +// nl3 = count(4,3,2); // Returns: [3,5,7,9] +// nl4 = count(5,reverse=true); // Returns: [4,3,2,1,0] +// nl5 = count(5,3,reverse=true); // Returns: [7,6,5,4,3] +function count(n,s=0,step=1,reverse=false) = let(n=is_list(n) ? len(n) : n) + reverse? [for (i=[n-1:-1:0]) s+i*step] + : [for (i=[0:1:n-1]) s+i*step]; + + +// Function: lerp() +// Usage: +// x = lerp(a, b, u); +// l = lerp(a, b, LIST); +// Description: +// Interpolate between two values or vectors. +// If `u` is given as a number, returns the single interpolated value. +// If `u` is 0.0, then the value of `a` is returned. +// If `u` is 1.0, then the value of `b` is returned. +// If `u` is a range, or list of numbers, returns a list of interpolated values. +// It is valid to use a `u` value outside the range 0 to 1. The result will be an extrapolation +// along the slope formed by `a` and `b`. +// Arguments: +// a = First value or vector. +// b = Second value or vector. +// u = The proportion from `a` to `b` to calculate. Standard range is 0.0 to 1.0, inclusive. If given as a list or range of values, returns a list of results. +// Example: +// x = lerp(0,20,0.3); // Returns: 6 +// x = lerp(0,20,0.8); // Returns: 16 +// x = lerp(0,20,-0.1); // Returns: -2 +// x = lerp(0,20,1.1); // Returns: 22 +// p = lerp([0,0],[20,10],0.25); // Returns [5,2.5] +// l = lerp(0,20,[0.4,0.6]); // Returns: [8,12] +// l = lerp(0,20,[0.25:0.25:0.75]); // Returns: [5,10,15] +// Example(2D): +// p1 = [-50,-20]; p2 = [50,30]; +// stroke([p1,p2]); +// pts = lerp(p1, p2, [0:1/8:1]); +// // Points colored in ROYGBIV order. +// rainbow(pts) translate($item) circle(d=3,$fn=8); +function lerp(a,b,u) = + assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") + is_finite(u)? (1-u)*a + u*b : + assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or valid range.") + [for (v = u) (1-v)*a + v*b ]; + + +// Function: lerpn() +// Usage: +// x = lerpn(a, b, n); +// x = lerpn(a, b, n, [endpoint]); +// Description: +// Returns exactly `n` values, linearly interpolated between `a` and `b`. +// If `endpoint` is true, then the last value will exactly equal `b`. +// If `endpoint` is false, then the last value will about `a+(b-a)*(1-1/n)`. +// Arguments: +// a = First value or vector. +// b = Second value or vector. +// n = The number of values to return. +// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less. +// Example: +// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4] +// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3] +// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1] +// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8] +function lerpn(a,b,n,endpoint=true) = + assert(same_shape(a,b), "Bad or inconsistent inputs to lerpn") + assert(is_int(n)) + assert(is_bool(endpoint)) + let( d = n - (endpoint? 1 : 0) ) + [for (i=[0:1:n-1]) let(u=i/d) (1-u)*a + u*b]; + + + +// Section: Miscellaneous Functions // Function: sqr() // Usage: @@ -139,124 +232,45 @@ function binomial_coefficient(n,k) = b[len(b)-1]; -// Function: lerp() +// Function: gcd() // Usage: -// x = lerp(a, b, u); -// l = lerp(a, b, LIST); +// x = gcd(a,b) // Description: -// Interpolate between two values or vectors. -// If `u` is given as a number, returns the single interpolated value. -// If `u` is 0.0, then the value of `a` is returned. -// If `u` is 1.0, then the value of `b` is returned. -// If `u` is a range, or list of numbers, returns a list of interpolated values. -// It is valid to use a `u` value outside the range 0 to 1. The result will be an extrapolation -// along the slope formed by `a` and `b`. -// Arguments: -// a = First value or vector. -// b = Second value or vector. -// u = The proportion from `a` to `b` to calculate. Standard range is 0.0 to 1.0, inclusive. If given as a list or range of values, returns a list of results. -// Example: -// x = lerp(0,20,0.3); // Returns: 6 -// x = lerp(0,20,0.8); // Returns: 16 -// x = lerp(0,20,-0.1); // Returns: -2 -// x = lerp(0,20,1.1); // Returns: 22 -// p = lerp([0,0],[20,10],0.25); // Returns [5,2.5] -// l = lerp(0,20,[0.4,0.6]); // Returns: [8,12] -// l = lerp(0,20,[0.25:0.25:0.75]); // Returns: [5,10,15] -// Example(2D): -// p1 = [-50,-20]; p2 = [50,30]; -// stroke([p1,p2]); -// pts = lerp(p1, p2, [0:1/8:1]); -// // Points colored in ROYGBIV order. -// rainbow(pts) translate($item) circle(d=3,$fn=8); -function lerp(a,b,u) = - assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") - is_finite(u)? (1-u)*a + u*b : - assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or valid range.") - [for (v = u) (1-v)*a + v*b ]; +// Computes the Greatest Common Divisor/Factor of `a` and `b`. +function gcd(a,b) = + assert(is_int(a) && is_int(b),"Arguments to gcd must be integers") + b==0 ? abs(a) : gcd(b,a % b); -// Function: lerpn() +// Computes lcm for two integers +function _lcm(a,b) = + assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm") + assert(a!=0 && b!=0, "Arguments to lcm should not be zero") + abs(a*b) / gcd(a,b); + + +// Computes lcm for a list of values +function _lcmlist(a) = + len(a)==1 ? a[0] : + _lcmlist(concat(lcm(a[0],a[1]),list_tail(a,2))); + + +// Function: lcm() // Usage: -// x = lerpn(a, b, n); -// x = lerpn(a, b, n, [endpoint]); +// div = lcm(a, b); +// divs = lcm(list); // Description: -// Returns exactly `n` values, linearly interpolated between `a` and `b`. -// If `endpoint` is true, then the last value will exactly equal `b`. -// If `endpoint` is false, then the last value will about `a+(b-a)*(1-1/n)`. -// Arguments: -// a = First value or vector. -// b = Second value or vector. -// n = The number of values to return. -// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less. -// Example: -// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4] -// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3] -// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1] -// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8] -function lerpn(a,b,n,endpoint=true) = - assert(same_shape(a,b), "Bad or inconsistent inputs to lerpn") - assert(is_int(n)) - assert(is_bool(endpoint)) - let( d = n - (endpoint? 1 : 0) ) - [for (i=[0:1:n-1]) let(u=i/d) (1-u)*a + u*b]; +// Computes the Least Common Multiple of the two arguments or a list of arguments. Inputs should +// be non-zero integers. The output is always a positive integer. It is an error to pass zero +// as an argument. +function lcm(a,b=[]) = + !is_list(a) && !is_list(b) + ? _lcm(a,b) + : let( arglist = concat(force_list(a),force_list(b)) ) + assert(len(arglist)>0, "Invalid call to lcm with empty list(s)") + _lcmlist(arglist); -// Section: Undef Safe Math - -// Function: u_add() -// Usage: -// x = u_add(a, b); -// Description: -// Adds `a` to `b`, returning the result, or undef if either value is `undef`. -// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. -// Arguments: -// a = First value. -// b = Second value. -function u_add(a,b) = is_undef(a) || is_undef(b)? undef : a + b; - - -// Function: u_sub() -// Usage: -// x = u_sub(a, b); -// Description: -// Subtracts `b` from `a`, returning the result, or undef if either value is `undef`. -// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. -// Arguments: -// a = First value. -// b = Second value. -function u_sub(a,b) = is_undef(a) || is_undef(b)? undef : a - b; - - -// Function: u_mul() -// Usage: -// x = u_mul(a, b); -// Description: -// Multiplies `a` by `b`, returning the result, or undef if either value is `undef`. -// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. -// Arguments: -// a = First value. -// b = Second value. -function u_mul(a,b) = - is_undef(a) || is_undef(b)? undef : - is_vector(a) && is_vector(b)? v_mul(a,b) : - a * b; - - -// Function: u_div() -// Usage: -// x = u_div(a, b); -// Description: -// Divides `a` by `b`, returning the result, or undef if either value is `undef`. -// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. -// Arguments: -// a = First value. -// b = Second value. -function u_div(a,b) = - is_undef(a) || is_undef(b)? undef : - is_vector(a) && is_vector(b)? v_div(a,b) : - a / b; - // Section: Hyperbolic Trigonometry @@ -488,6 +502,228 @@ function modang(x) = let(xx = posmod(x,360)) xx<180? xx : xx-360; + +// Section: Operations on Lists (Sums, Mean, Products) + +// Function: sum() +// Usage: +// x = sum(v, [dflt]); +// Description: +// Returns the sum of all entries in the given consistent list. +// If passed an array of vectors, returns the sum the vectors. +// If passed an array of matrices, returns the sum of the matrices. +// If passed an empty list, the value of `dflt` will be returned. +// Arguments: +// v = The list to get the sum of. +// dflt = The default value to return if `v` is an empty list. Default: 0 +// Example: +// sum([1,2,3]); // returns 6. +// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15] +function sum(v, dflt=0) = + v==[]? dflt : + assert(is_consistent(v), "Input to sum is non-numeric or inconsistent") + is_finite(v[0]) || is_vector(v[0]) ? [for(i=v) 1]*v : + _sum(v,v[0]*0); + +function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1); + + + + +// Function: mean() +// Usage: +// x = mean(v); +// Description: +// Returns the arithmetic mean/average of all entries in the given array. +// If passed a list of vectors, returns a vector of the mean of each part. +// Arguments: +// v = The list of values to get the mean of. +// Example: +// mean([2,3,4]); // returns 3. +// mean([[1,2,3], [3,4,5], [5,6,7]]); // returns [3, 4, 5] +function mean(v) = + assert(is_list(v) && len(v)>0, "Invalid list.") + sum(v)/len(v); + + + +// Function: median() +// Usage: +// middle = median(v) +// Description: +// Returns the median of the given vector. +function median(v) = + assert(is_vector(v), "Input to median must be a vector") + len(v)%2 ? max( list_smallest(v, ceil(len(v)/2)) ) : + let( lowest = list_smallest(v, len(v)/2 + 1), + max = max(lowest), + imax = search(max,lowest,1), + max2 = max([for(i=idx(lowest)) if(i!=imax[0]) lowest[i] ]) + ) + (max+max2)/2; + + +// Function: deltas() +// Usage: +// delts = deltas(v); +// Description: +// Returns a list with the deltas of adjacent entries in the given list, optionally wrapping back to the front. +// The list should be a consistent list of numeric components (numbers, vectors, matrix, etc). +// Given [a,b,c,d], returns [b-a,c-b,d-c]. +// +// Arguments: +// v = The list to get the deltas of. +// wrap = If true, wrap back to the start from the end. ie: return the difference between the last and first items as the last delta. Default: false +// Example: +// deltas([2,5,9,17]); // returns [3,4,8]. +// deltas([[1,2,3], [3,6,8], [4,8,11]]); // returns [[2,4,5], [1,2,3]] +function deltas(v, wrap=false) = + assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.") + [for (p=pair(v,wrap)) p[1]-p[0]] ; + + +// Function: cumsum() +// Usage: +// sums = cumsum(v); +// Description: +// Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list. +// If passed an array of vectors, returns a list of cumulative vectors sums. +// Arguments: +// v = The list to get the sum of. +// Example: +// cumsum([1,1,1]); // returns [1,2,3] +// cumsum([2,2,2]); // returns [2,4,6] +// cumsum([1,2,3]); // returns [1,3,6] +// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]] +function cumsum(v) = + assert(is_consistent(v), "The input is not consistent." ) + len(v)<=1 ? v : + _cumsum(v,_i=1,_acc=[v[0]]); + +function _cumsum(v,_i=0,_acc=[]) = + _i>=len(v) ? _acc : + _cumsum( v, _i+1, [ each _acc, _acc[len(_acc)-1] + v[_i] ] ); + + + +// Function: product() +// Usage: +// x = product(v); +// Description: +// Returns the product of all entries in the given list. +// If passed a list of vectors of same dimension, returns a vector of products of each part. +// If passed a list of square matrices, returns the resulting product matrix. +// Arguments: +// v = The list to get the product of. +// Example: +// product([2,3,4]); // returns 24. +// product([[1,2,3], [3,4,5], [5,6,7]]); // returns [15, 48, 105] +function product(v) = + assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)), + "Invalid input.") + _product(v, 1, v[0]); + +function _product(v, i=0, _tot) = + i>=len(v) ? _tot : + _product( v, + i+1, + ( is_vector(v[i])? v_mul(_tot,v[i]) : _tot*v[i] ) ); + + + +// Function: cumprod() +// Description: +// Returns a list where each item is the cumulative product of all items up to and including the corresponding entry in the input list. +// If passed an array of vectors, returns a list of elementwise vector products. If passed a list of square matrices returns matrix +// products multiplying on the left, so a list `[A,B,C]` will produce the output `[A,BA,CBA]`. +// Arguments: +// list = The list to get the product of. +// Example: +// cumprod([1,3,5]); // returns [1,3,15] +// cumprod([2,2,2]); // returns [2,4,8] +// cumprod([[1,2,3], [3,4,5], [5,6,7]])); // returns [[1, 2, 3], [3, 8, 15], [15, 48, 105]] +function cumprod(list) = + is_vector(list) ? _cumprod(list) : + assert(is_consistent(list), "Input must be a consistent list of scalars, vectors or square matrices") + is_matrix(list[0]) ? assert(len(list[0])==len(list[0][0]), "Matrices must be square") _cumprod(list) + : _cumprod_vec(list); + +function _cumprod(v,_i=0,_acc=[]) = + _i==len(v) ? _acc : + _cumprod( + v, _i+1, + concat( + _acc, + [_i==0 ? v[_i] : v[_i]*_acc[len(_acc)-1]] + ) + ); + +function _cumprod_vec(v,_i=0,_acc=[]) = + _i==len(v) ? _acc : + _cumprod_vec( + v, _i+1, + concat( + _acc, + [_i==0 ? v[_i] : v_mul(_acc[len(_acc)-1],v[_i])] + ) + ); + + + +// Function: convolve() +// Usage: +// x = convolve(p,q); +// Description: +// Given two vectors, or one vector and a path or +// two paths of the same dimension, finds the convolution of them. +// If both parameter are vectors, returns the vector convolution. +// If one parameter is a vector and the other a path, +// convolves using products by scalars and returns a path. +// If both parameters are paths, convolve using scalar products +// and returns a vector. +// The returned vector or path has length len(p)+len(q)-1. +// Arguments: +// p = The first vector or path. +// q = The second vector or path. +// Example: +// a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1] +// b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3] +// c = convolve([[1,1],[2,2],[3,1]],[1,2,1])); // Returns: [[1,1],[4,4],[8,6],[8,4],[3,1]] +// d = convolve([[1,1],[2,2],[3,1]],[[1,2],[2,1]])); // Returns: [3,9,11,7] +function convolve(p,q) = + p==[] || q==[] ? [] : + assert( (is_vector(p) || is_matrix(p)) + && ( is_vector(q) || (is_matrix(q) && ( !is_vector(p[0]) || (len(p[0])==len(q[0])) ) ) ) , + "The inputs should be vectors or paths all of the same dimension.") + let( n = len(p), + m = len(q)) + [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) ) + sum([for(j=[k1:k2]) p[i-j]*q[j] ]) + ]; + + + +// Function: sum_of_sines() +// Usage: +// sum_of_sines(a,sines) +// Description: +// Gives the sum of a series of sines, at a given angle. +// Arguments: +// a = Angle to get the value for. +// sines = List of [amplitude, frequency, offset] items, where the frequency is the number of times the cycle repeats around the circle. +// Example: +// v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]); +function sum_of_sines(a, sines) = + assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.") + sum([ for (s = sines) + let( + ss=point3d(s), + v=ss[0]*sin(a*ss[1]+ss[2]) + ) v + ]); + + + // Section: Random Number Generation // Function: rand_int() @@ -635,407 +871,6 @@ function random_polygon(n=3,size=1, seed) = -// Section: GCD/GCF, LCM - -// Function: gcd() -// Usage: -// x = gcd(a,b) -// Description: -// Computes the Greatest Common Divisor/Factor of `a` and `b`. -function gcd(a,b) = - assert(is_int(a) && is_int(b),"Arguments to gcd must be integers") - b==0 ? abs(a) : gcd(b,a % b); - - -// Computes lcm for two integers -function _lcm(a,b) = - assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm") - assert(a!=0 && b!=0, "Arguments to lcm should not be zero") - abs(a*b) / gcd(a,b); - - -// Computes lcm for a list of values -function _lcmlist(a) = - len(a)==1 ? a[0] : - _lcmlist(concat(lcm(a[0],a[1]),list_tail(a,2))); - - -// Function: lcm() -// Usage: -// div = lcm(a, b); -// divs = lcm(list); -// Description: -// Computes the Least Common Multiple of the two arguments or a list of arguments. Inputs should -// be non-zero integers. The output is always a positive integer. It is an error to pass zero -// as an argument. -function lcm(a,b=[]) = - !is_list(a) && !is_list(b) - ? _lcm(a,b) - : let( arglist = concat(force_list(a),force_list(b)) ) - assert(len(arglist)>0, "Invalid call to lcm with empty list(s)") - _lcmlist(arglist); - - - -// Section: Sums, Products, Aggregate Functions. - -// Function: sum() -// Usage: -// x = sum(v, [dflt]); -// Description: -// Returns the sum of all entries in the given consistent list. -// If passed an array of vectors, returns the sum the vectors. -// If passed an array of matrices, returns the sum of the matrices. -// If passed an empty list, the value of `dflt` will be returned. -// Arguments: -// v = The list to get the sum of. -// dflt = The default value to return if `v` is an empty list. Default: 0 -// Example: -// sum([1,2,3]); // returns 6. -// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15] -function sum(v, dflt=0) = - v==[]? dflt : - assert(is_consistent(v), "Input to sum is non-numeric or inconsistent") - is_finite(v[0]) || is_vector(v[0]) ? [for(i=v) 1]*v : - _sum(v,v[0]*0); - -function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1); - -// Function: cumsum() -// Usage: -// sums = cumsum(v); -// Description: -// Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list. -// If passed an array of vectors, returns a list of cumulative vectors sums. -// Arguments: -// v = The list to get the sum of. -// Example: -// cumsum([1,1,1]); // returns [1,2,3] -// cumsum([2,2,2]); // returns [2,4,6] -// cumsum([1,2,3]); // returns [1,3,6] -// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]] -function cumsum(v) = - assert(is_consistent(v), "The input is not consistent." ) - len(v)<=1 ? v : - _cumsum(v,_i=1,_acc=[v[0]]); - -function _cumsum(v,_i=0,_acc=[]) = - _i>=len(v) ? _acc : - _cumsum( v, _i+1, [ each _acc, _acc[len(_acc)-1] + v[_i] ] ); - - -// Function: sum_of_sines() -// Usage: -// sum_of_sines(a,sines) -// Description: -// Gives the sum of a series of sines, at a given angle. -// Arguments: -// a = Angle to get the value for. -// sines = List of [amplitude, frequency, offset] items, where the frequency is the number of times the cycle repeats around the circle. -// Example: -// v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]); -function sum_of_sines(a, sines) = - assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.") - sum([ for (s = sines) - let( - ss=point3d(s), - v=ss[0]*sin(a*ss[1]+ss[2]) - ) v - ]); - - -// Function: deltas() -// Usage: -// delts = deltas(v); -// Description: -// Returns a list with the deltas of adjacent entries in the given list, optionally wrapping back to the front. -// The list should be a consistent list of numeric components (numbers, vectors, matrix, etc). -// Given [a,b,c,d], returns [b-a,c-b,d-c]. -// -// Arguments: -// v = The list to get the deltas of. -// wrap = If true, wrap back to the start from the end. ie: return the difference between the last and first items as the last delta. Default: false -// Example: -// deltas([2,5,9,17]); // returns [3,4,8]. -// deltas([[1,2,3], [3,6,8], [4,8,11]]); // returns [[2,4,5], [1,2,3]] -function deltas(v, wrap=false) = - assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.") - [for (p=pair(v,wrap)) p[1]-p[0]] ; - - -// Function: product() -// Usage: -// x = product(v); -// Description: -// Returns the product of all entries in the given list. -// If passed a list of vectors of same dimension, returns a vector of products of each part. -// If passed a list of square matrices, returns the resulting product matrix. -// Arguments: -// v = The list to get the product of. -// Example: -// product([2,3,4]); // returns 24. -// product([[1,2,3], [3,4,5], [5,6,7]]); // returns [15, 48, 105] -function product(v) = - assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)), - "Invalid input.") - _product(v, 1, v[0]); - -function _product(v, i=0, _tot) = - i>=len(v) ? _tot : - _product( v, - i+1, - ( is_vector(v[i])? v_mul(_tot,v[i]) : _tot*v[i] ) ); - - - -// Function: cumprod() -// Description: -// Returns a list where each item is the cumulative product of all items up to and including the corresponding entry in the input list. -// If passed an array of vectors, returns a list of elementwise vector products. If passed a list of square matrices returns matrix -// products multiplying on the left, so a list `[A,B,C]` will produce the output `[A,BA,CBA]`. -// Arguments: -// list = The list to get the product of. -// Example: -// cumprod([1,3,5]); // returns [1,3,15] -// cumprod([2,2,2]); // returns [2,4,8] -// cumprod([[1,2,3], [3,4,5], [5,6,7]])); // returns [[1, 2, 3], [3, 8, 15], [15, 48, 105]] -function cumprod(list) = - is_vector(list) ? _cumprod(list) : - assert(is_consistent(list), "Input must be a consistent list of scalars, vectors or square matrices") - is_matrix(list[0]) ? assert(len(list[0])==len(list[0][0]), "Matrices must be square") _cumprod(list) - : _cumprod_vec(list); - -function _cumprod(v,_i=0,_acc=[]) = - _i==len(v) ? _acc : - _cumprod( - v, _i+1, - concat( - _acc, - [_i==0 ? v[_i] : v[_i]*_acc[len(_acc)-1]] - ) - ); - -function _cumprod_vec(v,_i=0,_acc=[]) = - _i==len(v) ? _acc : - _cumprod_vec( - v, _i+1, - concat( - _acc, - [_i==0 ? v[_i] : v_mul(_acc[len(_acc)-1],v[_i])] - ) - ); - - -// Function: mean() -// Usage: -// x = mean(v); -// Description: -// Returns the arithmetic mean/average of all entries in the given array. -// If passed a list of vectors, returns a vector of the mean of each part. -// Arguments: -// v = The list of values to get the mean of. -// Example: -// mean([2,3,4]); // returns 3. -// mean([[1,2,3], [3,4,5], [5,6,7]]); // returns [3, 4, 5] -function mean(v) = - assert(is_list(v) && len(v)>0, "Invalid list.") - sum(v)/len(v); - - - -// Function: median() -// Usage: -// middle = median(v) -// Description: -// Returns the median of the given vector. -function median(v) = - assert(is_vector(v), "Input to median must be a vector") - len(v)%2 ? max( list_smallest(v, ceil(len(v)/2)) ) : - let( lowest = list_smallest(v, len(v)/2 + 1), - max = max(lowest), - imax = search(max,lowest,1), - max2 = max([for(i=idx(lowest)) if(i!=imax[0]) lowest[i] ]) - ) - (max+max2)/2; - - -// Function: convolve() -// Usage: -// x = convolve(p,q); -// Description: -// Given two vectors, or one vector and a path or -// two paths of the same dimension, finds the convolution of them. -// If both parameter are vectors, returns the vector convolution. -// If one parameter is a vector and the other a path, -// convolves using products by scalars and returns a path. -// If both parameters are paths, convolve using scalar products -// and returns a vector. -// The returned vector or path has length len(p)+len(q)-1. -// Arguments: -// p = The first vector or path. -// q = The second vector or path. -// Example: -// a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1] -// b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3] -// c = convolve([[1,1],[2,2],[3,1]],[1,2,1])); // Returns: [[1,1],[4,4],[8,6],[8,4],[3,1]] -// d = convolve([[1,1],[2,2],[3,1]],[[1,2],[2,1]])); // Returns: [3,9,11,7] -function convolve(p,q) = - p==[] || q==[] ? [] : - assert( (is_vector(p) || is_matrix(p)) - && ( is_vector(q) || (is_matrix(q) && ( !is_vector(p[0]) || (len(p[0])==len(q[0])) ) ) ) , - "The inputs should be vectors or paths all of the same dimension.") - let( n = len(p), - m = len(q)) - [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) ) - sum([for(j=[k1:k2]) p[i-j]*q[j] ]) - ]; - - - - - - - -// Function: all_integer() -// Usage: -// bool = all_integer(x); -// Description: -// If given a number, returns true if the number is a finite integer. -// If given an empty list, returns false. If given a non-empty list, returns -// true if every item of the list is an integer. Otherwise, returns false. -// Arguments: -// x = The value to check. -// Example: -// b = all_integer(true); // Returns: false -// b = all_integer("foo"); // Returns: false -// b = all_integer(4); // Returns: true -// b = all_integer(4.5); // Returns: false -// b = all_integer([]); // Returns: false -// b = all_integer([3,4,5]); // Returns: true -// b = all_integer([3,4.2,5]); // Returns: false -// b = all_integer([3,[4,7],5]); // Returns: false -function all_integer(x) = - is_num(x)? is_int(x) : - is_list(x)? (x != [] && [for (xx=x) if(!is_int(xx)) 1] == []) : - false; - - - -// Function: any() -// Usage: -// bool = any(l); -// bool = any(l, func); // Requires OpenSCAD 2021.01 or later. -// Requirements: -// Requires OpenSCAD 2021.01 or later to use the `func=` argument. -// Description: -// Returns true if any item in list `l` evaluates as true. -// Arguments: -// l = The list to test for true items. -// func = An optional function literal of signature (x), returning bool, to test each list item with. -// Example: -// any([0,false,undef]); // Returns false. -// any([1,false,undef]); // Returns true. -// any([1,5,true]); // Returns true. -// any([[0,0], [0,0]]); // Returns true. -// any([[0,0], [1,0]]); // Returns true. -function any(l, func) = - assert(is_list(l), "The input is not a list." ) - assert(func==undef || is_func(func)) - is_func(func) - ? _any_func(l, func) - : _any_bool(l); - -function _any_func(l, func, i=0, out=false) = - i >= len(l) || out? out : - _any_func(l, func, i=i+1, out=out || func(l[i])); - -function _any_bool(l, i=0, out=false) = - i >= len(l) || out? out : - _any_bool(l, i=i+1, out=out || l[i]); - - -// Function: all() -// Usage: -// bool = all(l); -// bool = all(l, func); // Requires OpenSCAD 2021.01 or later. -// Requirements: -// Requires OpenSCAD 2021.01 or later to use the `func=` argument. -// Description: -// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal -// of signature (x), returning bool, then that function literal is evaluated for each list item. -// Arguments: -// l = The list to test for true items. -// func = An optional function literal of signature (x), returning bool, to test each list item with. -// Example: -// test1 = all([0,false,undef]); // Returns false. -// test2 = all([1,false,undef]); // Returns false. -// test3 = all([1,5,true]); // Returns true. -// test4 = all([[0,0], [0,0]]); // Returns true. -// test5 = all([[0,0], [1,0]]); // Returns true. -// test6 = all([[1,1], [1,1]]); // Returns true. -function all(l, func) = - assert(is_list(l), "The input is not a list.") - assert(func==undef || is_func(func)) - is_func(func) - ? _all_func(l, func) - : _all_bool(l); - -function _all_func(l, func, i=0, out=true) = - i >= len(l) || !out? out : - _all_func(l, func, i=i+1, out=out && func(l[i])); - -function _all_bool(l, i=0, out=true) = - i >= len(l) || !out? out : - _all_bool(l, i=i+1, out=out && l[i]); - - -// Function: count_true() -// Usage: -// seq = count_true(l, [nmax=]); -// seq = count_true(l, func, [nmax=]); // Requires OpenSCAD 2021.01 or later. -// Requirements: -// Requires OpenSCAD 2021.01 or later to use the `func=` argument. -// Description: -// Returns the number of items in `l` that evaluate as true. -// If `l` is a lists of lists, this is applied recursively to each -// sublist. Returns the total count of items that evaluate as true -// in all recursive sublists. -// Arguments: -// l = The list to test for true items. -// func = An optional function literal of signature (x), returning bool, to test each list item with. -// --- -// nmax = Max number of true items to count. Default: `undef` (no limit) -// Example: -// num1 = count_true([0,false,undef]); // Returns 0. -// num2 = count_true([1,false,undef]); // Returns 1. -// num3 = count_true([1,5,false]); // Returns 2. -// num4 = count_true([1,5,true]); // Returns 3. -// num5 = count_true([[0,0], [0,0]]); // Returns 2. -// num6 = count_true([[0,0], [1,0]]); // Returns 2. -// num7 = count_true([[1,1], [1,1]]); // Returns 2. -// num8 = count_true([[1,1], [1,1]], nmax=1); // Returns 1. -function count_true(l, func, nmax) = - assert(is_list(l)) - assert(func==undef || is_func(func)) - is_func(func) - ? _count_true_func(l, func, nmax) - : _count_true_bool(l, nmax); - -function _count_true_func(l, func, nmax, i=0, out=0) = - i >= len(l) || (nmax!=undef && out>=nmax) ? out : - _count_true_func( - l, func, nmax, i = i + 1, - out = out + (func(l[i])? 1:0) - ); - -function _count_true_bool(l, nmax, i=0, out=0) = - i >= len(l) || (nmax!=undef && out>=nmax) ? out : - _count_true_bool( - l, nmax, i = i + 1, - out = out + (l[i]? 1:0) - ); - - // Section: Calculus // Function: deriv() @@ -1609,4 +1444,5 @@ function _rootfind(f, xpts, ypts, yrange, tol, i=0) = : _rootfind(f, xinterval, yinterval, new_yrange, tol, i+1); + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/screw_drive.scad b/screw_drive.scad index 5598bc2..96feec6 100644 --- a/screw_drive.scad +++ b/screw_drive.scad @@ -127,7 +127,6 @@ function phillips_diam(size, depth) = - // Module: torx_mask() // Usage: // torx_mask(size, l, [center]); @@ -144,7 +143,7 @@ function phillips_diam(size, depth) = // torx_mask(size=30, l=10, $fa=1, $fs=1); module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { anchor = get_anchor(anchor, center, BOT, BOT); - od = torx_outer_diam(size); + od = torx_diam(size); attachable(anchor,spin,orient, d=od, l=l) { linear_extrude(height=l, convexity=4, center=true) { torx_mask2d(size); @@ -164,10 +163,10 @@ module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) { // Example(2D): // torx_mask2d(size=30, $fa=1, $fs=1); module torx_mask2d(size) { - od = torx_outer_diam(size); - id = torx_inner_diam(size); - tip = torx_tip_radius(size); - rounding = torx_rounding_radius(size); + od = torx_diam(size); + id = _torx_inner_diam(size); + tip = _torx_tip_radius(size); + rounding = _torx_rounding_radius(size); base = od - 2*tip; $fn = quantup(segs(od/2),12); difference() { @@ -194,13 +193,13 @@ module torx_mask2d(size) { } -// Function: torx_outer_diam() +// Function: torx_diam() // Usage: -// diam = torx_outer_diam(size); +// diam = torx_diam(size); // Description: Get the typical outer diameter of Torx profile. // Arguments: // size = Torx size. -function torx_outer_diam(size) = lookup(size, [ +function torx_diam(size) = lookup(size, [ [ 6, 1.75], [ 8, 2.40], [ 10, 2.80], @@ -220,13 +219,13 @@ function torx_outer_diam(size) = lookup(size, [ ]); -// Function: torx_inner_diam() -// Usage: -// diam = torx_inner_diam(size); -// Description: Get typical inner diameter of Torx profile. -// Arguments: -// size = Torx size. -function torx_inner_diam(size) = lookup(size, [ +/// Internal Function: torx_inner_diam() +/// Usage: +/// diam = torx_inner_diam(size); +/// Description: Get typical inner diameter of Torx profile. +/// Arguments: +/// size = Torx size. +function _torx_inner_diam(size) = lookup(size, [ [ 6, 1.27], [ 8, 1.75], [ 10, 2.05], @@ -272,13 +271,13 @@ function torx_depth(size) = lookup(size, [ ]); -// Function: torx_tip_radius() -// Usage: -// rad = torx_tip_radius(size); -// Description: Gets minor rounding radius of Torx profile. -// Arguments: -// size = Torx size. -function torx_tip_radius(size) = lookup(size, [ +/// Internal Function: torx_tip_radius() +/// Usage: +/// rad = torx_tip_radius(size); +/// Description: Gets minor rounding radius of Torx profile. +/// Arguments: +/// size = Torx size. +function _torx_tip_radius(size) = lookup(size, [ [ 6, 0.132], [ 8, 0.190], [ 10, 0.229], @@ -298,13 +297,13 @@ function torx_tip_radius(size) = lookup(size, [ ]); -// Function: torx_rounding_radius() -// Usage: -// rad = torx_rounding_radius(size); -// Description: Gets major rounding radius of Torx profile. -// Arguments: -// size = Torx size. -function torx_rounding_radius(size) = lookup(size, [ +/// Internal Function: torx_rounding_radius() +/// Usage: +/// rad = torx_rounding_radius(size); +/// Description: Gets major rounding radius of Torx profile. +/// Arguments: +/// size = Torx size. +function _torx_rounding_radius(size) = lookup(size, [ [ 6, 0.383], [ 8, 0.510], [ 10, 0.598], diff --git a/tests/README.txt b/tests/README.txt new file mode 100644 index 0000000..972db22 --- /dev/null +++ b/tests/README.txt @@ -0,0 +1,3 @@ +This directory contains regression tests scripts to check whether the +library is working correctly. If all the scripts run without +producing any output, then all the tests have passed. diff --git a/tests/test_mutators.scad b/tests/test_mutators.scad index b706486..c3f107a 100644 --- a/tests/test_mutators.scad +++ b/tests/test_mutators.scad @@ -1,7 +1,7 @@ include <../std.scad> -module test_HSL() { +module test_hsl() { for (h = [0:30:360]) { for (s = [0:0.2:1]) { for (l = [0:0.2:1]) { @@ -16,15 +16,15 @@ module test_HSL() { h<=300? [x,0,c] : [c,0,x] ); - assert_approx(HSL(h,s,l), rgb, format("h={}, s={}, l={}", [h,s,l])); + assert_approx(hsl(h,s,l), rgb, format("h={}, s={}, l={}", [h,s,l])); } } } } -test_HSL(); +test_hsl(); -module test_HSV() { +module test_hsv() { for (h = [0:30:360]) { for (s = [0:0.2:1]) { for (v = [0:0.2:1]) { @@ -39,12 +39,12 @@ module test_HSV() { h<=300? [x,0,c] : [c,0,x] ); - assert_approx(HSV(h,s,v), rgb, format("h={}, s={}, v={}", [h,s,v])); + assert_approx(hsv(h,s,v), rgb, format("h={}, s={}, v={}", [h,s,v])); } } } } -test_HSV(); +test_hsv(); // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_screw_drive.scad b/tests/test_screw_drive.scad index 398a43e..e71cda0 100644 --- a/tests/test_screw_drive.scad +++ b/tests/test_screw_drive.scad @@ -2,24 +2,24 @@ include <../std.scad> include <../screw_drive.scad> -module test_torx_outer_diam() { - assert_approx(torx_outer_diam(10), 2.80); - assert_approx(torx_outer_diam(15), 3.35); - assert_approx(torx_outer_diam(20), 3.95); - assert_approx(torx_outer_diam(25), 4.50); - assert_approx(torx_outer_diam(30), 5.60); - assert_approx(torx_outer_diam(40), 6.75); +module test_torx_diam() { + assert_approx(torx_diam(10), 2.80); + assert_approx(torx_diam(15), 3.35); + assert_approx(torx_diam(20), 3.95); + assert_approx(torx_diam(25), 4.50); + assert_approx(torx_diam(30), 5.60); + assert_approx(torx_diam(40), 6.75); } -test_torx_outer_diam(); +test_torx_diam(); module test_torx_inner_diam() { - assert_approx(torx_inner_diam(10), 2.05); - assert_approx(torx_inner_diam(15), 2.40); - assert_approx(torx_inner_diam(20), 2.85); - assert_approx(torx_inner_diam(25), 3.25); - assert_approx(torx_inner_diam(30), 4.05); - assert_approx(torx_inner_diam(40), 4.85); + assert_approx(_torx_inner_diam(10), 2.05); + assert_approx(_torx_inner_diam(15), 2.40); + assert_approx(_torx_inner_diam(20), 2.85); + assert_approx(_torx_inner_diam(25), 3.25); + assert_approx(_torx_inner_diam(30), 4.05); + assert_approx(_torx_inner_diam(40), 4.85); } test_torx_inner_diam(); @@ -36,23 +36,23 @@ test_torx_depth(); module test_torx_tip_radius() { - assert_approx(torx_tip_radius(10), 0.229); - assert_approx(torx_tip_radius(15), 0.267); - assert_approx(torx_tip_radius(20), 0.305); - assert_approx(torx_tip_radius(25), 0.375); - assert_approx(torx_tip_radius(30), 0.451); - assert_approx(torx_tip_radius(40), 0.546); + assert_approx(_torx_tip_radius(10), 0.229); + assert_approx(_torx_tip_radius(15), 0.267); + assert_approx(_torx_tip_radius(20), 0.305); + assert_approx(_torx_tip_radius(25), 0.375); + assert_approx(_torx_tip_radius(30), 0.451); + assert_approx(_torx_tip_radius(40), 0.546); } test_torx_tip_radius(); module test_torx_rounding_radius() { - assert_approx(torx_rounding_radius(10), 0.598); - assert_approx(torx_rounding_radius(15), 0.716); - assert_approx(torx_rounding_radius(20), 0.859); - assert_approx(torx_rounding_radius(25), 0.920); - assert_approx(torx_rounding_radius(30), 1.194); - assert_approx(torx_rounding_radius(40), 1.428); + assert_approx(_torx_rounding_radius(10), 0.598); + assert_approx(_torx_rounding_radius(15), 0.716); + assert_approx(_torx_rounding_radius(20), 0.859); + assert_approx(_torx_rounding_radius(25), 0.920); + assert_approx(_torx_rounding_radius(30), 1.194); + assert_approx(_torx_rounding_radius(40), 1.428); } test_torx_rounding_radius(); diff --git a/tutorials/Mutators.md b/tutorials/Mutators.md index a2730be..6da07c3 100644 --- a/tutorials/Mutators.md +++ b/tutorials/Mutators.md @@ -287,7 +287,7 @@ shell2d(thickness=-5,or=[5,0],ir=[5,0]) star(5,step=2,d=100); ## Color Manipulators The built-in OpenSCAD `color()` module can let you set the RGB color of an object, but it's often easier to select colors using other color schemes. You can use the HSL or Hue-Saturation-Lightness -color scheme with the `HSL()` module: +color scheme with the `hsl()` module: ```openscad-3D include @@ -295,12 +295,12 @@ n = 10; size = 100/n; for (a=count(n), b=count(n), c=count(n)) { let( h=360*a/n, s=1-b/(n-1), l=c/(n-1)) translate(size*[a,b,c]) { - HSL(h,s,l) cube(size); + hsl(h,s,l) cube(size); } } ``` -You can use the HSV or Hue-Saturation-Value color scheme with the `HSV()` module: +You can use the HSV or Hue-Saturation-Value color scheme with the `hsv()` module: ```openscad-3D include @@ -308,7 +308,7 @@ n = 10; size = 100/n; for (a=count(n), b=count(n), c=count(n)) { let( h=360*a/n, s=1-b/(n-1), v=c/(n-1)) translate(size*[a,b,c]) { - HSV(h,s,v) cube(size); + hsv(h,s,v) cube(size); } } ``` diff --git a/utility.scad b/utility.scad index 0b04681..978f538 100644 --- a/utility.scad +++ b/utility.scad @@ -116,6 +116,30 @@ function is_int(n) = is_finite(n) && n == round(n); function is_integer(n) = is_finite(n) && n == round(n); +// Function: all_integer() +// Usage: +// bool = all_integer(x); +// Description: +// If given a number, returns true if the number is a finite integer. +// If given an empty list, returns false. If given a non-empty list, returns +// true if every item of the list is an integer. Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// b = all_integer(true); // Returns: false +// b = all_integer("foo"); // Returns: false +// b = all_integer(4); // Returns: true +// b = all_integer(4.5); // Returns: false +// b = all_integer([]); // Returns: false +// b = all_integer([3,4,5]); // Returns: true +// b = all_integer([3,4.2,5]); // Returns: false +// b = all_integer([3,[4,7],5]); // Returns: false +function all_integer(x) = + is_num(x)? is_int(x) : + is_list(x)? (x != [] && [for (xx=x) if(!is_int(xx)) 1] == []) : + false; + + // Function: is_nan() // Usage: // bool = is_nan(x); @@ -404,6 +428,181 @@ function all_defined(v,recursive=false) = +// Section: Undef Safe Arithmetic + +// Function: u_add() +// Usage: +// x = u_add(a, b); +// Description: +// Adds `a` to `b`, returning the result, or undef if either value is `undef`. +// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. +// Arguments: +// a = First value. +// b = Second value. +function u_add(a,b) = is_undef(a) || is_undef(b)? undef : a + b; + + +// Function: u_sub() +// Usage: +// x = u_sub(a, b); +// Description: +// Subtracts `b` from `a`, returning the result, or undef if either value is `undef`. +// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. +// Arguments: +// a = First value. +// b = Second value. +function u_sub(a,b) = is_undef(a) || is_undef(b)? undef : a - b; + + +// Function: u_mul() +// Usage: +// x = u_mul(a, b); +// Description: +// Multiplies `a` by `b`, returning the result, or undef if either value is `undef`. +// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. +// Arguments: +// a = First value. +// b = Second value. +function u_mul(a,b) = + is_undef(a) || is_undef(b)? undef : + is_vector(a) && is_vector(b)? v_mul(a,b) : + a * b; + + +// Function: u_div() +// Usage: +// x = u_div(a, b); +// Description: +// Divides `a` by `b`, returning the result, or undef if either value is `undef`. +// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020. +// Arguments: +// a = First value. +// b = Second value. +function u_div(a,b) = + is_undef(a) || is_undef(b)? undef : + is_vector(a) && is_vector(b)? v_div(a,b) : + a / b; + + + +// Section: Boolean list testing + +// Function: any() +// Usage: +// bool = any(l); +// bool = any(l, func); // Requires OpenSCAD 2021.01 or later. +// Requirements: +// Requires OpenSCAD 2021.01 or later to use the `func=` argument. +// Description: +// Returns true if any item in list `l` evaluates as true. +// Arguments: +// l = The list to test for true items. +// func = An optional function literal of signature (x), returning bool, to test each list item with. +// Example: +// any([0,false,undef]); // Returns false. +// any([1,false,undef]); // Returns true. +// any([1,5,true]); // Returns true. +// any([[0,0], [0,0]]); // Returns true. +// any([[0,0], [1,0]]); // Returns true. +function any(l, func) = + assert(is_list(l), "The input is not a list." ) + assert(func==undef || is_func(func)) + is_func(func) + ? _any_func(l, func) + : _any_bool(l); + +function _any_func(l, func, i=0, out=false) = + i >= len(l) || out? out : + _any_func(l, func, i=i+1, out=out || func(l[i])); + +function _any_bool(l, i=0, out=false) = + i >= len(l) || out? out : + _any_bool(l, i=i+1, out=out || l[i]); + + +// Function: all() +// Usage: +// bool = all(l); +// bool = all(l, func); // Requires OpenSCAD 2021.01 or later. +// Requirements: +// Requires OpenSCAD 2021.01 or later to use the `func=` argument. +// Description: +// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal +// of signature (x), returning bool, then that function literal is evaluated for each list item. +// Arguments: +// l = The list to test for true items. +// func = An optional function literal of signature (x), returning bool, to test each list item with. +// Example: +// test1 = all([0,false,undef]); // Returns false. +// test2 = all([1,false,undef]); // Returns false. +// test3 = all([1,5,true]); // Returns true. +// test4 = all([[0,0], [0,0]]); // Returns true. +// test5 = all([[0,0], [1,0]]); // Returns true. +// test6 = all([[1,1], [1,1]]); // Returns true. +function all(l, func) = + assert(is_list(l), "The input is not a list.") + assert(func==undef || is_func(func)) + is_func(func) + ? _all_func(l, func) + : _all_bool(l); + +function _all_func(l, func, i=0, out=true) = + i >= len(l) || !out? out : + _all_func(l, func, i=i+1, out=out && func(l[i])); + +function _all_bool(l, i=0, out=true) = + i >= len(l) || !out? out : + _all_bool(l, i=i+1, out=out && l[i]); + + +// Function: count_true() +// Usage: +// seq = count_true(l, [nmax=]); +// seq = count_true(l, func, [nmax=]); // Requires OpenSCAD 2021.01 or later. +// Requirements: +// Requires OpenSCAD 2021.01 or later to use the `func=` argument. +// Description: +// Returns the number of items in `l` that evaluate as true. +// If `l` is a lists of lists, this is applied recursively to each +// sublist. Returns the total count of items that evaluate as true +// in all recursive sublists. +// Arguments: +// l = The list to test for true items. +// func = An optional function literal of signature (x), returning bool, to test each list item with. +// --- +// nmax = Max number of true items to count. Default: `undef` (no limit) +// Example: +// num1 = count_true([0,false,undef]); // Returns 0. +// num2 = count_true([1,false,undef]); // Returns 1. +// num3 = count_true([1,5,false]); // Returns 2. +// num4 = count_true([1,5,true]); // Returns 3. +// num5 = count_true([[0,0], [0,0]]); // Returns 2. +// num6 = count_true([[0,0], [1,0]]); // Returns 2. +// num7 = count_true([[1,1], [1,1]]); // Returns 2. +// num8 = count_true([[1,1], [1,1]], nmax=1); // Returns 1. +function count_true(l, func, nmax) = + assert(is_list(l)) + assert(func==undef || is_func(func)) + is_func(func) + ? _count_true_func(l, func, nmax) + : _count_true_bool(l, nmax); + +function _count_true_func(l, func, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_func( + l, func, nmax, i = i + 1, + out = out + (func(l[i])? 1:0) + ); + +function _count_true_bool(l, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_bool( + l, nmax, i = i + 1, + out = out + (l[i]? 1:0) + ); + + + // Section: Processing Arguments to Functions and Modules