From b9ae9e9a8b320d961ba3e4089ddb6ab1127d6ea9 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 27 Mar 2022 23:06:42 -0400 Subject: [PATCH] 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