From f598426344099b13f89e78716ee1be27616a14f6 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 23 Oct 2020 19:06:30 -0400 Subject: [PATCH 01/42] Add cumprod function and regression test --- math.scad | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_math.scad | 22 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/math.scad b/math.scad index c0ac6ab..6cb1362 100644 --- a/math.scad +++ b/math.scad @@ -617,6 +617,44 @@ function _product(v, i=0, _tot) = +// 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 in the order items appear in the list. +// 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] : _acc[len(_acc)-1]*v[_i]] + ) + ); + +function _cumprod_vec(v,_i=0,_acc=[]) = + _i==len(v) ? _acc : + _cumprod_vec( + v, _i+1, + concat( + _acc, + [_i==0 ? v[_i] : vmul(_acc[len(_acc)-1],v[_i])] + ) + ); + + // Function: outer_product() // Usage: // x = outer_product(u,v); diff --git a/tests/test_math.scad b/tests/test_math.scad index 824098a..1d9fee9 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -836,6 +836,28 @@ module test_linear_solve(){ test_linear_solve(); +module test_cumprod(){ + assert_equal(cumprod([1,2,3,4]), [1,2,6,24]); + assert_equal(cumprod([4]), [4]); + assert_equal(cumprod([]),[]); + assert_equal(cumprod([[2,3],[4,5],[6,7]]), [[2,3],[8,15],[48,105]]); + assert_equal(cumprod([[5,6,7]]),[[5,6,7]]); + assert_equal(cumprod([ + [[1,2],[3,4]], + [[-4,5],[6,4]], + [[9,-3],[4,3]] + ]), + [ + [[1,2],[3,4]], + [[8,13],[12,31]], + [[124,15],[232,57]] + ]); + assert_equal(cumprod([[[1,2],[3,4]]]), [[[1,2],[3,4]]]); +} +test_cumprod(); + + + module test_outer_product(){ assert_equal(outer_product([1,2,3],[4,5,6]), [[4,5,6],[8,10,12],[12,15,18]]); assert_equal(outer_product([1,2],[4,5,6]), [[4,5,6],[8,10,12]]); From 1e193a090ec322916820dffef1d13f1cdca4aeef Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 25 Oct 2020 15:36:30 -0700 Subject: [PATCH 02/42] Echo no longer works with non-breaking spaces. --- strings.scad | 12 +++++------- structs.scad | 2 +- version.scad | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/strings.scad b/strings.scad index 26df2b7..6b7053d 100644 --- a/strings.scad +++ b/strings.scad @@ -626,7 +626,6 @@ function is_letter(s) = // Arguments: // fmt = The formatting string, with placeholders to format the values into. // vals = The list of values to format. -// use_nbsp = Pad fields with HTML entity ` ` instead of spaces. // Example(NORENDER): // str_format("The value of {} is {:.14f}.", ["pi", PI]); // Returns: "The value of pi is 3.14159265358979." // str_format("The value {1:f} is known as {0}.", ["pi", PI]); // Returns: "The value 3.141593 is known as pi." @@ -634,7 +633,7 @@ function is_letter(s) = // str_format("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // Returns: "foo 12000true" // str_format("{:-10s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostamus27.440" // str_format("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostam 27.440" -function str_format(fmt, vals, use_nbsp=false) = +function str_format(fmt, vals) = let( parts = str_split(fmt,"{") ) str_join([ @@ -676,7 +675,7 @@ function str_format(fmt, vals, use_nbsp=false) = typ=="G"? upcase(fmt_float(val,default(prec,6))) : assert(false,str("Unknown format type: ",typ)), padlen = max(0,wid-len(unpad)), - padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : use_nbsp? " " : " "]), + padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : " "]), out = left? str(unpad, padfill) : str(padfill, unpad) ) out, raw @@ -692,7 +691,6 @@ function str_format(fmt, vals, use_nbsp=false) = // Arguments: // fmt = The formatting string, with placeholders to format the values into. // vals = The list of values to format. -// use_nbsp = Pad fields with HTML entity ` ` instead of spaces. // Example(NORENDER): // echofmt("The value of {} is {:.14f}.", ["pi", PI]); // ECHO: "The value of pi is 3.14159265358979." // echofmt("The value {1:f} is known as {0}.", ["pi", PI]); // ECHO: "The value 3.141593 is known as pi." @@ -700,10 +698,10 @@ function str_format(fmt, vals, use_nbsp=false) = // echofmt("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // ECHO: "foo 12000true" // echofmt("{:-10s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostamus27.440" // echofmt("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostam 27.440" -function echofmt(fmt, vals, use_nbsp=false) = echo(str_format(fmt,vals,use_nbsp)); -module echofmt(fmt, vals, use_nbsp=false) { +function echofmt(fmt, vals) = echo(str_format(fmt,vals)); +module echofmt(fmt, vals) { no_children($children); - echo(str_format(fmt,vals,use_nbsp)); + echo(str_format(fmt,vals)); } // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/structs.scad b/structs.scad index 1e9f321..15831ce 100644 --- a/structs.scad +++ b/structs.scad @@ -96,7 +96,7 @@ function struct_keys(struct) = // struct = input structure // name = optional structure name to list at the top of the output. Default: "" function struct_echo(struct,name="") = - let( keylist = [for(entry=struct) str("  ",entry[0],": ",entry[1],"\n")]) + let( keylist = [for(entry=struct) str(" ",entry[0],": ",entry[1],"\n")]) echo(str("\nStructure ",name,"\n",str_join(keylist))) undef; diff --git a/version.scad b/version.scad index b3a3b8b..134628c 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,459]; +BOSL_VERSION = [2,0,460]; // Section: BOSL Library Version Functions From 40fde0f2b88ca1967aa0e1b96716c14ba518a164 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Thu, 29 Oct 2020 16:46:07 -0700 Subject: [PATCH 03/42] Fix for cuboid() issue #307 --- shapes.scad | 17 +++++++++-------- version.scad | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/shapes.scad b/shapes.scad index 054053f..f695ff2 100644 --- a/shapes.scad +++ b/shapes.scad @@ -78,14 +78,15 @@ module cuboid( cnt = sum(e); r = first_defined([chamfer, rounding, 0]); c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)]; + c2 = vmul(corner,c/2); $fn = is_finite(chamfer)? 4 : segs(r); - translate(vmul(corner,size/2-c)) { + translate(vmul(corner, size/2-c)) { if (cnt == 0) { - cube(c*2, center=true); + translate(c2) cube(c, center=true); } else if (cnt == 1) { - if (e.x) xcyl(l=c.x*2, r=r); - if (e.y) ycyl(l=c.y*2, r=r); - if (e.z) zcyl(l=c.z*2, r=r); + if (e.x) right(c2.x) xcyl(l=c.x, r=r); + if (e.y) back (c2.y) ycyl(l=c.y, r=r); + if (e.z) up (c2.z) zcyl(l=c.z, r=r); } else if (cnt == 2) { if (!e.x) { intersection() { @@ -152,9 +153,9 @@ module cuboid( } else { isize = [for (v = size) max(0.001, v-2*chamfer)]; hull() { - cube([size.x, isize.y, isize.z], center=true); - cube([isize.x, size.y, isize.z], center=true); - cube([isize.x, isize.y, size.z], center=true); + cube([ size.x, isize.y, isize.z], center=true); + cube([isize.x, size.y, isize.z], center=true); + cube([isize.x, isize.y, size.z], center=true); } } } else if (chamfer<0) { diff --git a/version.scad b/version.scad index 134628c..ea08daa 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,460]; +BOSL_VERSION = [2,0,461]; // Section: BOSL Library Version Functions From c2d5080d336cefd61652860bd97ea9d0ab5e16e3 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sat, 7 Nov 2020 14:23:09 -0800 Subject: [PATCH 04/42] Remove undef math warnings from tube() --- shapes.scad | 21 +++++++++++++++++---- version.scad | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/shapes.scad b/shapes.scad index f695ff2..c219731 100644 --- a/shapes.scad +++ b/shapes.scad @@ -894,11 +894,24 @@ module tube( anchor, spin=0, orient=UP, center, realign=false, l ) { + function safe_add(x,wall) = is_undef(x)? undef : x+wall; h = first_defined([h,l,1]); - r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]); - r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]); - ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]); - ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]); + orr1 = get_radius( + r=first_defined([or1, r1, or, r]), + d=first_defined([od1, d1, od, d]), + dflt=undef + ); + orr2 = get_radius( + r=first_defined([or2, r2, or, r]), + d=first_defined([od2, d2, od, d]), + dflt=undef + ); + irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef); + irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef); + r1 = is_num(orr1)? orr1 : is_num(irr1)? irr1+wall : undef; + r2 = is_num(orr2)? orr2 : is_num(irr2)? irr2+wall : undef; + ir1 = is_num(irr1)? irr1 : is_num(orr1)? orr1-wall : undef; + ir2 = is_num(irr2)? irr2 : is_num(orr2)? orr2-wall : undef; assert(ir1 <= r1, "Inner radius is larger than outer radius."); assert(ir2 <= r2, "Inner radius is larger than outer radius."); sides = segs(max(r1,r2)); diff --git a/version.scad b/version.scad index ea08daa..ac55581 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,461]; +BOSL_VERSION = [2,0,462]; // Section: BOSL Library Version Functions From 914e57cc6e3119c738808bd5a77b77e944679a31 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 11 Nov 2020 17:45:12 -0800 Subject: [PATCH 05/42] Fix for nested diff()s, issue #270 --- attachments.scad | 9 ++++++--- version.scad | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/attachments.scad b/attachments.scad index b8ffa9f..36ad4b3 100644 --- a/attachments.scad +++ b/attachments.scad @@ -923,7 +923,8 @@ module attachable( $parent_geom = geom; $parent_size = attach_geom_size(geom); $attach_to = undef; - if (attachment_is_shown($tags)) { + do_show = attachment_is_shown($tags); + if (do_show) { if (is_undef($color)) { children(0); } else color($color) { @@ -1270,7 +1271,7 @@ module recolor(c) // Usage: // hide(tags) ... // Description: -// Hides all children with the given tags. +// Hides all children with the given tags. Overrides any previous `hide()` or `show()` calls. // Example: // hide("A") cube(50, anchor=CENTER, $tags="Main") { // attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A"); @@ -1279,6 +1280,7 @@ module recolor(c) module hide(tags="") { $tags_hidden = tags==""? [] : str_split(tags, " "); + $tags_shown = []; children(); } @@ -1287,7 +1289,7 @@ module hide(tags="") // Usage: // show(tags) ... // Description: -// Shows only children with the given tags. +// Shows only children with the given tags. Overrides any previous `hide()` or `show()` calls. // Example: // show("A B") cube(50, anchor=CENTER, $tags="Main") { // attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A"); @@ -1296,6 +1298,7 @@ module hide(tags="") module show(tags="") { $tags_shown = tags==""? [] : str_split(tags, " "); + $tags_hidden = []; children(); } diff --git a/version.scad b/version.scad index ac55581..a676d9a 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,462]; +BOSL_VERSION = [2,0,463]; // Section: BOSL Library Version Functions From 74615a9fe0e6cec7501847afd5e19547491fb380 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 12 Nov 2020 09:19:25 -0500 Subject: [PATCH 06/42] rot_decode bugfix: relax check to approximate for last row of transformation matrix. --- affine.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affine.scad b/affine.scad index 462ae1f..dbedf1f 100644 --- a/affine.scad +++ b/affine.scad @@ -486,7 +486,7 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ // rot_decode(left(12)*xrot(-33)); // Returns [33, [-1,0,0], [0,0,0], [-12,0,0]] // rot_decode(translate([3,4,5])); // Returns [0, [0,0,1], [0,0,0], [3,4,5]] function rot_decode(M) = - assert(is_matrix(M,4,4) && M[3]==[0,0,0,1], "Input matrix must be a 4x4 matrix representing a 3d transformation") + assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation") let(R = submatrix(M,[0:2],[0:2])) assert(approx(det3(R),1) && approx(norm_fro(R * transpose(R)-ident(3)),0),"Input matrix is not a rotation") let( From 4cda1bf4888210d4d9716ddab9b636e662916a76 Mon Sep 17 00:00:00 2001 From: Marcin Jaworski Date: Fri, 13 Nov 2020 17:10:04 +0100 Subject: [PATCH 07/42] Added ability to return default value from struct_val() if keyword is not present --- structs.scad | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/structs.scad b/structs.scad index 15831ce..c37d62a 100644 --- a/structs.scad +++ b/structs.scad @@ -64,16 +64,17 @@ function struct_remove(struct, keyword) = // Function: struct_val() // Usage: -// struct_val(struct,keyword) +// struct_val(struct, keyword, default) // Description: -// Returns the value for the specified keyword in the structure, or undef if the keyword is not present +// Returns the value for the specified keyword in the structure, or default value if the keyword is not present // Arguments: // struct = input structure // keyword = keyword whose value to return -function struct_val(struct,keyword) = +// default = default value to return if keyword is not present, defaults to undef +function struct_val(struct, keyword, default=undef) = assert(is_def(keyword),"keyword is missing") let(ind = search([keyword],struct)[0]) - ind == [] ? undef : struct[ind][1]; + ind == [] ? default : struct[ind][1]; // Function: struct_keys() From 8cd753074318c0a39b50a9c5aecadc684e741b26 Mon Sep 17 00:00:00 2001 From: Marcin Jaworski Date: Fri, 13 Nov 2020 17:19:29 +0100 Subject: [PATCH 08/42] Added tests for struct_val() with default value --- tests/test_structs.scad | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_structs.scad b/tests/test_structs.scad index a4a4d92..fdb8d35 100644 --- a/tests/test_structs.scad +++ b/tests/test_structs.scad @@ -27,6 +27,9 @@ module test_struct_val() { assert(struct_val(st,"Foo") == 91); assert(struct_val(st,"Bar") == 28); assert(struct_val(st,"Baz") == 9); + assert(struct_val(st,"Baz",5) == 9); + assert(struct_val(st,"Qux") == undef); + assert(struct_val(st,"Qux",5) == 5); } test_struct_val(); From 3793f69c3f54ff5d3c8d95782cc67239fac1aed9 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 16 Nov 2020 15:32:26 -0800 Subject: [PATCH 09/42] Added mod_indent() and mod_trace() --- debug.scad | 42 ++++++++++++++++++++++++++++++++++++++++-- version.scad | 2 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/debug.scad b/debug.scad index ada55e8..545e25e 100644 --- a/debug.scad +++ b/debug.scad @@ -254,11 +254,19 @@ module debug_polyhedron(points, faces, convexity=10, txtsize=1, disabled=false) // Function: standard_anchors() +// Usage: +// anchs = standard_anchors(); // Description: // Return the vectors for all standard anchors. -function standard_anchors() = [ +// Arguments: +// two_d = If true, returns only the anchors where the Z component is 0. Default: false +function standard_anchors(two_d=false) = [ for ( - zv = [TOP, CENTER, BOTTOM], + zv = [ + if (!two_d) TOP, + CENTER, + if (!two_d) BOTTOM + ], yv = [FRONT, CENTER, BACK], xv = [LEFT, CENTER, RIGHT] ) xv+yv+zv @@ -448,5 +456,35 @@ module ruler(length=100, width=undef, thickness=1, depth=3, labels=false, pipsca } +// Function: mod_indent() +// Usage: +// str = mod_indent(); +// Description: +// Returns a string that is the total indentation for the module level you are at. +// Arguments: +// indent = The string to indent each level by. Default: " " (Two spaces) +// Example: +// x = echo(str(mod_indent(), parent_module(0))); +function mod_indent(indent=" ") = + str_join([for (i=[1:1:$parent_modules-1]) indent]); + + +// Function: mod_trace() +// Usage: +// str = mod_trace(, ); +// Description: +// Returns a string that shows the current module and its parents, indented for each unprinted parent module. +// Arguments: +// levs = This is the number of levels to print the names of. Prints the N most nested module names. Default: 2 +// indent = The string to indent each level by. Default: " " (Two spaces) +// modsep = Multiple module names will be separated by this string. Default: "->" +// Example: +// x = echo(mod_trace()); +function mod_trace(levs=2, indent=" ", modsep="->") = + str( + str_join([for (i=[1:1:$parent_modules+1-levs]) indent]), + str_join([for (i=[min(levs-1,$parent_modules-1):-1:0]) parent_module(i)], modsep) + ); + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/version.scad b/version.scad index a676d9a..c6d9bde 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,463]; +BOSL_VERSION = [2,0,464]; // Section: BOSL Library Version Functions From bf80c1193a9f7bc09bce448c328d0ef140f32bc0 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 16 Nov 2020 15:43:45 -0800 Subject: [PATCH 10/42] Fixups for hulling() --- attachments.scad | 48 ++++++++++++++++++++++++------------------------ version.scad | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/attachments.scad b/attachments.scad index 36ad4b3..112f173 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1308,13 +1308,12 @@ module show(tags="") // diff(neg, [keep]) ... // diff(neg, pos, [keep]) ... // Description: -// If `neg` is given, takes the union of all children with tags -// that are in `neg`, and differences them from the union of all -// children with tags in `pos`. If `pos` is not given, then all -// items in `neg` are differenced from all items not in `neg`. If -// `keep` is given, all children with tags in `keep` are then unioned -// with the result. If `keep` is not given, all children without -// tags in `pos` or `neg` are then unioned with the result. +// If `neg` is given, takes the union of all children with tags that are in `neg`, and differences +// them from the union of all children with tags in `pos`. If `pos` is not given, then all items in +// `neg` are differenced from all items not in `neg`. If `keep` is given, all children with tags in +// `keep` are then unioned with the result. If `keep` is not given, all children without tags in +// `pos` or `neg` are then unioned with the result. +// Cannot be used in conjunction with `intersect()` or `hulling()` on the same parent object. // Arguments: // neg = String containing space delimited set of tag names of children to difference away. // pos = String containing space delimited set of tag names of children to be differenced away from. @@ -1367,14 +1366,12 @@ module diff(neg, pos=undef, keep=undef) // intersect(a, [keep]) ... // intersect(a, b, [keep]) ... // Description: -// If `a` is given, takes the union of all children with tags that -// are in `a`, and intersection()s them with the union of all -// children with tags in `b`. If `b` is not given, then the union -// of all items with tags in `a` are intersection()ed with the union -// of all items without tags in `a`. If `keep` is given, then the -// result is unioned with all the children with tags in `keep`. If -// `keep` is not given, all children without tags in `a` or `b` are -// unioned with the result. +// If `a` is given, takes the union of all children with tags that are in `a`, and `intersection()`s +// them with the union of all children with tags in `b`. If `b` is not given, then the union of all +// items with tags in `a` are intersection()ed with the union of all items without tags in `a`. If +// `keep` is given, then the result is unioned with all the children with tags in `keep`. If `keep` +// is not given, all children without tags in `a` or `b` are unioned with the result. +// Cannot be used in conjunction with `diff()` or `hulling()` on the same parent object. // Arguments: // a = String containing space delimited set of tag names of children. // b = String containing space delimited set of tag names of children. @@ -1413,15 +1410,14 @@ module intersect(a, b=undef, keep=undef) // Module: hulling() // Usage: -// hulling(a, [keep]) ... +// hulling(a) ... // Description: -// Takes the union of all children with tags that are in `a`, and hull()s them. -// If `keep` is given, then the result is unioned with all the children with -// tags in `keep`. If `keep` is not given, all children without tags in `a` are -// unioned with the result. +// If `a` is not given, then all children are `hull()`ed together. +// If `a` is given as a string, then all children with `$tags` that are in `a` are +// `hull()`ed together and the result is then unioned with all the remaining children. +// Cannot be used in conjunction with `diff()` or `intersect()` on the same parent object. // Arguments: -// a = String containing space delimited set of tag names of children. -// keep = String containing space delimited set of tag names of children to keep whole. +// a = String containing space delimited set of tag names of children to hull. // Example: // hulling("body") // sphere(d=100, $tags="body") { @@ -1430,8 +1426,12 @@ module intersect(a, b=undef, keep=undef) // } module hulling(a) { - hull() show(a) children(); - children(); + if (is_undef(a)) { + hull() children(); + } else { + hull() show(a) children(); + children(); + } } diff --git a/version.scad b/version.scad index c6d9bde..f0e7380 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,464]; +BOSL_VERSION = [2,0,465]; // Section: BOSL Library Version Functions From e4f249786363676419c360a23c3dda3cf86077af Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 16 Nov 2020 17:50:08 -0800 Subject: [PATCH 11/42] Changes all references to polyline to path. --- beziers.scad | 42 +++++++++++++-------------- debug.scad | 28 +++++++++--------- joiners.scad | 3 +- paths.scad | 76 +++++++++++++------------------------------------ regions.scad | 8 +++--- rounding.scad | 3 +- shapes2d.scad | 2 +- skin.scad | 3 +- std.scad | 1 + transforms.scad | 6 ++-- version.scad | 2 +- wiring.scad | 4 +-- 12 files changed, 67 insertions(+), 111 deletions(-) diff --git a/beziers.scad b/beziers.scad index a8484b6..af9675e 100644 --- a/beziers.scad +++ b/beziers.scad @@ -9,10 +9,8 @@ ////////////////////////////////////////////////////////////////////// -include - // Section: Terminology -// **Polyline**: A series of points joined by straight line segements. +// **Path**: A series of points joined by straight line segements. // . // **Bezier Curve**: A mathematical curve that joins two endpoints, following a curve determined by one or more control points. // . @@ -27,7 +25,7 @@ include // . // **Bezier Path**: A list of bezier segments flattened out into a list of points, where each segment shares the endpoint of the previous segment as a start point. A cubic Bezier Path looks something like: // `[endpt1, cp1, cp2, endpt2, cp3, cp4, endpt3]` -// **NOTE**: A bezier path is *NOT* a polyline. It is only the points and controls used to define the curve. +// **NOTE**: A "bezier path" is *NOT* a standard path. It is only the points and controls used to define the curve. // . // **Bezier Patch**: A surface defining grid of (N+1) by (N+1) bezier points. If a Bezier Segment defines a curved line, a Bezier Patch defines a curved surface. // . @@ -374,7 +372,7 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) = // p0 = [40, 0]; // p1 = [0, 0]; // p2 = [30, 30]; -// trace_polyline([p0,p1,p2], showpts=true, size=0.5, color="green"); +// trace_path([p0,p1,p2], showpts=true, size=0.5, color="green"); // fbez = fillet3pts(p0,p1,p2, 10); // trace_bezier(slice(fbez, 1, -2), size=1); function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let( @@ -482,11 +480,11 @@ function bezier_path_length(path, N=3, max_deflect=0.001) = -// Function: bezier_polyline() +// Function: bezier_path() // Usage: -// bezier_polyline(bezier, [splinesteps], [N]) +// bezier_path(bezier, [splinesteps], [N]) // Description: -// Takes a bezier path and converts it into a polyline. +// Takes a bezier path and converts it into a path of points. // Arguments: // bezier = A bezier path to approximate. // splinesteps = Number of straight lines to split each bezier segment into. default=16 @@ -498,9 +496,9 @@ function bezier_path_length(path, N=3, max_deflect=0.001) = // [60,25], [70,0], [80,-25], // [80,-50], [50,-50] // ]; -// trace_polyline(bez, size=1, N=3, showpts=true); -// trace_polyline(bezier_polyline(bez, N=3), size=3); -function bezier_polyline(bezier, splinesteps=16, N=3) = +// trace_path(bez, size=1, N=3, showpts=true); +// trace_path(bezier_path(bez, N=3), size=3); +function bezier_path(bezier, splinesteps=16, N=3) = assert(is_path(bezier)) assert(is_int(N)) assert(is_int(splinesteps)) @@ -598,15 +596,15 @@ function path_to_bezier(path, tangents, size, relsize, uniform=false, closed=fal // Usage: // fillet_path(pts, fillet, [maxerr]); // Description: -// Takes a 3D polyline path and fillets the corners, returning a 3d cubic (degree 3) bezier path. +// Takes a 3D path and fillets the corners, returning a 3d cubic (degree 3) bezier path. // Arguments: -// pts = 3D Polyline path to fillet. -// fillet = The radius to fillet/round the polyline corners by. +// pts = 3D path to fillet. +// fillet = The radius to fillet/round the path corners by. // maxerr = Max amount bezier curve should diverge from actual radius curve. Default: 0.1 // Example(2D): // pline = [[40,0], [0,0], [35,35], [0,70], [-10,60], [-5,55], [0,60]]; // bez = fillet_path(pline, 10); -// trace_polyline(pline, showpts=true, size=0.5, color="green"); +// trace_path(pline, showpts=true, size=0.5, color="green"); // trace_bezier(bez, size=1); function fillet_path(pts, fillet, maxerr=0.1) = concat( [pts[0], pts[0]], @@ -722,7 +720,7 @@ module bezier_polygon(bezier, splinesteps=16, N=3) { assert(is_int(N)); assert(is_int(splinesteps)); assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); - polypoints=bezier_polyline(bezier, splinesteps, N); + polypoints=bezier_path(bezier, splinesteps, N); polygon(points=slice(polypoints, 0, -1)); } @@ -803,7 +801,7 @@ module rotate_sweep_bezier(bezier, splinesteps=16, N=3, convexity=undef, angle=3 assert(is_int(N)); assert(is_num(angle)); assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); - oline = bezier_polyline(bezier, splinesteps=splinesteps, N=N); + oline = bezier_path(bezier, splinesteps=splinesteps, N=N); maxx = max([for (pt = oline) abs(pt[0])]); miny = min(subindex(oline,1)); maxy = max(subindex(oline,1)); @@ -839,7 +837,7 @@ module bezier_path_extrude(bezier, splinesteps=16, N=3, convexity=undef, clipsiz assert(is_int(N)); assert(is_num(clipsize)); assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); - path = slice(bezier_polyline(bezier, splinesteps, N), 0, -1); + path = slice(bezier_path(bezier, splinesteps, N), 0, -1); path_extrude(path, convexity=convexity, clipsize=clipsize) children(); } @@ -876,8 +874,8 @@ module bezier_sweep_bezier(bezier, path, pathsteps=16, bezsteps=16, bezN=3, path assert(is_int(pathN)); assert(len(bezier)%bezN == 1, str("For argument bezier, a degree ",bezN," bezier path shound have a multiple of ",bezN," points in it, plus 1.")); assert(len(path)%pathN == 1, str("For argument bezier, a degree ",pathN," bezier path shound have a multiple of ",pathN," points in it, plus 1.")); - bez_points = simplify_path(bezier_polyline(bezier, bezsteps, bezN)); - path_points = simplify_path(path3d(bezier_polyline(path, pathsteps, pathN))); + bez_points = simplify_path(bezier_path(bezier, bezsteps, bezN)); + path_points = simplify_path(path3d(bezier_path(path, pathsteps, pathN))); path_sweep(bez_points, path_points); } @@ -902,8 +900,8 @@ module trace_bezier(bez, N=3, size=1) { assert(is_path(bez)); assert(is_int(N)); assert(len(bez)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); - trace_polyline(bez, N=N, showpts=true, size=size, color="green"); - trace_polyline(bezier_polyline(bez, N=N), size=size, color="cyan"); + trace_path(bez, N=N, showpts=true, size=size, color="green"); + trace_path(bezier_path(bez, N=N), size=size, color="cyan"); } diff --git a/debug.scad b/debug.scad index 545e25e..c2568c9 100644 --- a/debug.scad +++ b/debug.scad @@ -8,32 +8,30 @@ // ``` ////////////////////////////////////////////////////////////////////// -include - // Section: Debugging Paths and Polygons -// Module: trace_polyline() +// Module: trace_path() // Description: -// Renders lines between each point of a polyline path. +// Renders lines between each point of a path. // Can also optionally show the individual vertex points. // Arguments: -// pline = The array of points in the polyline. +// path = The list of points in the path. // closed = If true, draw the segment from the last vertex to the first. Default: false // showpts = If true, draw vertices and control points. // N = Mark the first and every Nth vertex after in a different color and shape. // size = Diameter of the lines drawn. // color = Color to draw the lines (but not vertices) in. // Example(FlatSpin): -// polyline = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]]; -// trace_polyline(polyline, showpts=true, size=0.5, color="lightgreen"); -module trace_polyline(pline, closed=false, showpts=false, N=1, size=1, color="yellow") { - assert(is_path(pline),"Input pline is not a path"); +// path = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]]; +// trace_path(path, showpts=true, size=0.5, color="lightgreen"); +module trace_path(path, closed=false, showpts=false, N=1, size=1, color="yellow") { + assert(is_path(path),"Invalid path argument"); sides = segs(size/2); - pline = closed? close_path(pline) : pline; + path = closed? close_path(path) : path; if (showpts) { - for (i = [0:1:len(pline)-1]) { - translate(pline[i]) { + for (i = [0:1:len(path)-1]) { + translate(path[i]) { if (i%N == 0) { color("blue") sphere(d=size*2.5, $fn=8); } else { @@ -47,11 +45,11 @@ module trace_polyline(pline, closed=false, showpts=false, N=1, size=1, color="ye } } if (N!=3) { - color(color) stroke(path3d(pline), width=size, $fn=8); + color(color) stroke(path3d(path), width=size, $fn=8); } else { - for (i = [0:1:len(pline)-2]) { + for (i = [0:1:len(path)-2]) { if (N!=3 || (i%N) != 1) { - color(color) extrude_from_to(pline[i], pline[i+1]) circle(d=size, $fn=sides); + color(color) extrude_from_to(path[i], path[i+1]) circle(d=size, $fn=sides); } } } diff --git a/joiners.scad b/joiners.scad index f68b124..38594a8 100644 --- a/joiners.scad +++ b/joiners.scad @@ -10,7 +10,6 @@ include -include // Section: Half Joiners @@ -989,7 +988,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 : let(side_smooth=select(pin_smooth, 0, 2)) concat(side_smooth, [socket_smooth], reverse(side_smooth)); bez = path_to_bezier(path,relsize=smoothing,tangents=tangent); - rounded = bezier_polyline(bez,splinesteps=splinesteps); + rounded = bezier_path(bez,splinesteps=splinesteps); bounds = pointlist_bounds(rounded); //kk = search([bounds[1].y], subindex(rounded,1)); //echo(rounded[kk[0]]); diff --git a/paths.scad b/paths.scad index e0e7c95..ac95666 100644 --- a/paths.scad +++ b/paths.scad @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////// // LibFile: paths.scad -// Polylines, polygons and paths. +// Support for polygons and paths. // To use, add the following lines to the beginning of your file: // ``` // include @@ -421,7 +421,7 @@ function path_torsion(path, closed=false) = // cp = Centerpoint of spiral. Default: `[0,0]` // scale = [X,Y] scaling factors for each axis. Default: `[1,1]` // Example(3D): -// trace_polyline(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true); +// trace_path(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true); function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let( rr=get_radius(r=r, d=d, dflt=100), cnt=floor(turns*n), @@ -435,44 +435,6 @@ function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let( ]; -// Function: points_along_path3d() -// Usage: -// points_along_path3d(polyline, path); -// Description: -// Calculates the vertices needed to create a `polyhedron()` of the -// extrusion of `polyline` along `path`. The closed 2D path shold be -// centered on the XY plane. The 2D path is extruded perpendicularly -// along the 3D path. Produces a list of 3D vertices. Vertex count -// is `len(polyline)*len(path)`. Gives all the reoriented vertices -// for `polyline` at the first point in `path`, then for the second, -// and so on. -// Arguments: -// polyline = A closed list of 2D path points. -// path = A list of 3D path points. -function points_along_path3d( - polyline, // The 2D polyline to drag along the 3D path. - path, // The 3D polyline path to follow. - q=Q_Ident(), // Used in recursion - n=0 // Used in recursion -) = let( - end = len(path)-1, - v1 = (n == 0)? [0, 0, 1] : unit(path[n]-path[n-1]), - v2 = (n == end)? unit(path[n]-path[n-1]) : unit(path[n+1]-path[n]), - crs = cross(v1, v2), - axis = norm(crs) <= 0.001? [0, 0, 1] : crs, - ang = vector_angle(v1, v2), - hang = ang * (n==0? 1.0 : 0.5), - hrot = Quat(axis, hang), - arot = Quat(axis, ang), - roth = Q_Mul(hrot, q), - rotm = Q_Mul(arot, q) -) concat( - [for (i = [0:1:len(polyline)-1]) Qrot(roth,p=point3d(polyline[i])) + path[n]], - (n == end)? [] : points_along_path3d(polyline, path, rotm, n+1) -); - - - // Function: path_self_intersections() // Usage: // isects = path_self_intersections(path, [eps]); @@ -529,9 +491,9 @@ function path_self_intersections(path, closed=true, eps=EPSILON) = // Function: split_path_at_self_crossings() // Usage: -// polylines = split_path_at_self_crossings(path, [closed], [eps]); +// paths = split_path_at_self_crossings(path, [closed], [eps]); // Description: -// Splits a path into polyline sections wherever the path crosses itself. +// Splits a path into sub-paths wherever the original path crosses itself. // Splits may occur mid-segment, so new vertices will be created at the intersection points. // Arguments: // path = The path to split up. @@ -539,8 +501,8 @@ function path_self_intersections(path, closed=true, eps=EPSILON) = // eps = Acceptable variance. Default: `EPSILON` (1e-9) // Example(2D): // path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ]; -// polylines = split_path_at_self_crossings(path); -// rainbow(polylines) stroke($item, closed=false, width=2); +// paths = split_path_at_self_crossings(path); +// rainbow(paths) stroke($item, closed=false, width=2); function split_path_at_self_crossings(path, closed=true, eps=EPSILON) = let( path = cleanup_path(path, eps=eps), @@ -681,11 +643,11 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) = // Usage: // assemble_a_path_from_fragments(subpaths); // Description: -// Given a list of incomplete paths, assembles them together into one complete closed path, and +// Given a list of paths, assembles them together into one complete closed polygon path, and // remainder fragments. Returns [PATH, FRAGMENTS] where FRAGMENTS is the list of remaining -// polyline path fragments. +// unused path fragments. // Arguments: -// fragments = List of polylines to be assembled into complete polygons. +// fragments = List of paths to be assembled into complete polygons. // rightmost = If true, assemble paths using rightmost turns. Leftmost if false. // startfrag = The fragment to start with. Default: 0 // eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) @@ -738,9 +700,9 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, // Usage: // assemble_path_fragments(subpaths); // Description: -// Given a list of incomplete paths, assembles them together into complete closed paths if it can. +// Given a list of paths, assembles them together into complete closed polygon paths if it can. // Arguments: -// fragments = List of polylines to be assembled into complete polygons. +// fragments = List of paths to be assembled into complete polygons. // eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) function assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) = len(fragments)==0? _finished : @@ -832,10 +794,10 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic // Module: spiral_sweep() // Description: -// Takes a closed 2D polyline path, centered on the XY plane, and -// extrudes it along a 3D spiral path of a given radius, height and twist. +// Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path +// of a given radius, height and twist. // Arguments: -// polyline = Array of points of a polyline path, to be extruded. +// path = Array of points of a polygon path, to be extruded. // h = height of the spiral to extrude along. // r = Radius of the spiral to extrude along. Default: 50 // d = Diameter of the spiral to extrude along. @@ -847,10 +809,10 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic // Example: // poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; // spiral_sweep(poly, h=200, r=50, twist=1080, $fn=36); -module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient=UP) { +module spiral_sweep(poly, h, r, twist=360, center, d, anchor, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=50); - polyline = path3d(polyline); - pline_count = len(polyline); + poly = path3d(poly); + pline_count = len(poly); steps = ceil(segs(r)*(twist/360)); anchor = get_anchor(anchor,center,BOT,BOT); @@ -863,7 +825,7 @@ module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient dy = r*sin(a), dz = h * (p/steps), pts = apply_list( - polyline, [ + poly, [ affine3d_xrot(90), affine3d_zrot(a), affine3d_translate([dx, dy, dz-h/2]) @@ -902,7 +864,7 @@ module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient // Module: path_extrude() // Description: -// Extrudes 2D children along a 3D polyline path. This may be slow. +// Extrudes 2D children along a 3D path. This may be slow. // Arguments: // path = array of points for the bezier path to extrude along. // convexity = maximum number of walls a ran can pass through. diff --git a/regions.scad b/regions.scad index b55a4f1..0a45673 100644 --- a/regions.scad +++ b/regions.scad @@ -154,9 +154,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([ // Function: split_path_at_region_crossings() // Usage: -// polylines = split_path_at_region_crossings(path, region, [eps]); +// paths = split_path_at_region_crossings(path, region, [eps]); // Description: -// Splits a path into polyline sections wherever the path crosses the perimeter of a region. +// Splits a path into sub-paths wherever the path crosses the perimeter of a region. // Splits may occur mid-segment, so new vertices will be created at the intersection points. // Arguments: // path = The path to split up. @@ -166,9 +166,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([ // Example(2D): // path = square(50,center=false); // region = [circle(d=80), circle(d=40)]; -// polylines = split_path_at_region_crossings(path, region); +// paths = split_path_at_region_crossings(path, region); // color("#aaa") region(region); -// rainbow(polylines) stroke($item, closed=false, width=2); +// rainbow(paths) stroke($item, closed=false, width=2); function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON) = let( path = deduplicate(path, eps=eps), diff --git a/rounding.scad b/rounding.scad index 876c420..2a3516c 100644 --- a/rounding.scad +++ b/rounding.scad @@ -181,7 +181,6 @@ include // path_sweep(regular_ngon(n=36,or=.1),round_corners(list2,closed=false, method="circle", cut = 0.75)); // Example(FlatSpin): Rounding a spiral with increased rounding along the length // // Construct a square spiral path in 3D -// include // $fn=36; // square = [[0,0],[1,0],[1,1],[0,1]]; // spiral = flatten(repeat(concat(square,reverse(square)),5)); // Squares repeat 10 times, forward and backward @@ -454,7 +453,7 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals let ( bez = path_to_bezier(path, tangents=tangents, size=size, relsize=relsize, uniform=uniform, closed=closed) ) - bezier_polyline(bez,splinesteps=splinesteps); + bezier_path(bez,splinesteps=splinesteps); diff --git a/shapes2d.scad b/shapes2d.scad index 6b5a6dd..4eda015 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -377,7 +377,7 @@ module stroke( // stroke(closed=true, path); // Example(FlatSpin): // path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]); -// trace_polyline(path, showpts=true, color="cyan"); +// trace_path(path, showpts=true, color="cyan"); function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false) = // First try for 2D arc specified by width and thickness is_def(width) && is_def(thickness)? ( diff --git a/skin.scad b/skin.scad index 37b5c51..1c1c45a 100644 --- a/skin.scad +++ b/skin.scad @@ -10,7 +10,6 @@ // - https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad ////////////////////////////////////////////////////////////////////// -include // Section: Skinning @@ -824,7 +823,7 @@ function associate_vertices(polygons, split, curpoly=0) = // Function&Module: sweep() // Usage: As Module -// sweep(shape, transformations, ) +// sweep(shape, transformations, , ) // Usage: As Function // vnf = sweep(shape, transformations, , ); // Description: diff --git a/std.scad b/std.scad index 685b6ab..94d80a0 100644 --- a/std.scad +++ b/std.scad @@ -31,6 +31,7 @@ include include include include +include include include include diff --git a/transforms.scad b/transforms.scad index e9cfafd..43cb257 100644 --- a/transforms.scad +++ b/transforms.scad @@ -671,8 +671,8 @@ function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p) // // Example: Scaling Points // path = xrot(90,p=path3d(circle(d=50,$fn=12))); -// #trace_polyline(path); -// trace_polyline(zscale(2,p=path)); +// #trace_path(path); +// trace_path(zscale(2,p=path)); module zscale(z=1) scale([1,1,z]) children(); function zscale(z=1, p=undef) = scale([1,1,z],p=p); @@ -917,7 +917,7 @@ function zflip(z=0,p) = // color("blue") move_copies(pts) circle(d=3, $fn=8); // Example(FlatSpin): Calling as a 3D Function // pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3); -// trace_polyline(close_path(pts), showpts=true); +// trace_path(close_path(pts), showpts=true); module skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0) { multmatrix( diff --git a/version.scad b/version.scad index f0e7380..9d44c69 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,465]; +BOSL_VERSION = [2,0,466]; // Section: BOSL Library Version Functions diff --git a/wiring.scad b/wiring.scad index bc970d2..d16120a 100644 --- a/wiring.scad +++ b/wiring.scad @@ -72,7 +72,7 @@ function hex_offsets(n, d, lev=0, arr=[]) = // Usage: // wiring(path, wires, [wirediam], [rounding], [wirenum], [bezsteps]); // Arguments: -// path = The 3D polyline path that the wire bundle should follow. +// path = The 3D path that the wire bundle should follow. // wires = The number of wires in the wiring bundle. // wirediam = The diameter of each wire in the bundle. // rounding = The radius that the path corners will be rounded to. @@ -90,7 +90,7 @@ module wiring(path, wires, wirediam=2, rounding=10, wirenum=0, bezsteps=12) { ]; offsets = hex_offsets(wires, wirediam); bezpath = fillet_path(path, rounding); - poly = simplify_path(path3d(bezier_polyline(bezpath, bezsteps))); + poly = simplify_path(path3d(bezier_path(bezpath, bezsteps))); n = max(segs(wirediam), 8); r = wirediam/2; for (i = [0:1:wires-1]) { From 9d5d7cbc4ff3c97602afd083553d8800aec1a981 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 17 Nov 2020 14:25:08 -0800 Subject: [PATCH 12/42] Enhanced sweep() to accept region input. --- regions.scad | 4 ++-- skin.scad | 40 +++++++++++++++++++++++++++------------- version.scad | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/regions.scad b/regions.scad index 0a45673..acd66d9 100644 --- a/regions.scad +++ b/regions.scad @@ -285,8 +285,8 @@ function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) = vnfs = [ if (vnf != EMPTY_VNF) vnf, for (rgn = regions) let( - cleaved = _cleave_simple_region(rgn), - face = is_undef(transform)? cleaved : apply(transform,path3d(cleaved)), + cleaved = path3d(_cleave_simple_region(rgn)), + face = is_undef(transform)? cleaved : apply(transform,cleaved), faceidxs = reverse? [for (i=[len(face)-1:-1:0]) i] : [for (i=[0:1:len(face)-1]) i] ) [face, [faceidxs]] ], diff --git a/skin.scad b/skin.scad index 1c1c45a..dc91e36 100644 --- a/skin.scad +++ b/skin.scad @@ -873,19 +873,33 @@ function associate_vertices(polygons, split, curpoly=0) = // sweep(shape, concat(outside,inside)); function sweep(shape, transformations, closed=false, caps) = - assert(is_list_of(transformations, ident(4)), "Input transformations must be a list of numeric 4x4 matrices in sweep") - assert(is_path(shape,2), "Input shape must be a 2d path") - let( - caps = is_def(caps) ? caps : - closed ? false : true, - capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])), - fullcaps = is_bool(caps) ? [caps,caps] : caps - ) - assert(len(transformations), "transformation must be length 2 or more") - assert(len(shape)>=3, "shape must be a path of at least 3 points") - assert(capsOK, "caps must be boolean or a list of two booleans") - assert(!closed || !caps, "Cannot make closed shape with caps") - _skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps); + assert(is_list_of(transformations, ident(4)), "Input transformations must be a list of numeric 4x4 matrices in sweep") + assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.") + let( + caps = is_def(caps) ? caps : + closed ? false : true, + capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])), + fullcaps = is_bool(caps) ? [caps,caps] : caps + ) + assert(len(transformations), "transformation must be length 2 or more") + assert(capsOK, "caps must be boolean or a list of two booleans") + assert(!closed || !caps, "Cannot make closed shape with caps") + is_region(shape)? let( + regions = split_nested_region(shape), + rtrans = reverse(transformations), + vnfs = [ + for (rgn=regions) each [ + for (path=select(rgn,0,-1)) + sweep(path, transformations, closed=closed, caps=false), + if (fullcaps[0]) region_faces(rgn, reverse=true), + if (fullcaps[1]) region_faces(rgn, transform=select(transformations,-1)), + ], + ], + vnf = vnf_merge(vnfs) + ) vnf : + assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points") + _skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps); + module sweep(shape, transformations, closed=false, caps, convexity=10, anchor="origin",cp,spin=0, orient=UP, extent=false) diff --git a/version.scad b/version.scad index 9d44c69..796f139 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,466]; +BOSL_VERSION = [2,0,467]; // Section: BOSL Library Version Functions From 1e93276747baee5af7ea4fab2efc400007b4d4b5 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 17 Nov 2020 14:41:14 -0800 Subject: [PATCH 13/42] Enable using path_sweep() with a region. --- skin.scad | 40 ++++++++++++++++++++-------------------- version.scad | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/skin.scad b/skin.scad index dc91e36..ce50f9c 100644 --- a/skin.scad +++ b/skin.scad @@ -823,11 +823,11 @@ function associate_vertices(polygons, split, curpoly=0) = // Function&Module: sweep() // Usage: As Module -// sweep(shape, transformations, , ) +// sweep(shape, transforms, , ) // Usage: As Function -// vnf = sweep(shape, transformations, , ); +// vnf = sweep(shape, transforms, , ); // Description: -// The input `shape` must be a non-self-intersecting polygon in two dimensions, and `transformations` +// The input `shape` must be a non-self-intersecting 2D polygon or region, and `transforms` // is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence // to the shape input and links the resulting polygons together to form a polyhedron. // If `closed=true` then the first and last transformation are linked together. @@ -841,8 +841,8 @@ function associate_vertices(polygons, split, curpoly=0) = // in your model, but will arise if you add a second object to the model. This may mislead you into // thinking the second object caused a problem. Even adding a simple cube to the model will reveal the problem. // Arguments: -// shape = 2d path describing shape to be swept -// transformations = list of 4x4 matrices to apply +// shape = 2d path or region, describing the shape to be swept. +// transforms = list of 4x4 matrices to apply // closed = set to true to form a closed (torus) model. Default: false // caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false. // convexity = convexity setting for use with polyhedron. (module only) Default: 10 @@ -872,8 +872,8 @@ function associate_vertices(polygons, split, curpoly=0) = // inside = [for(i=[24:-1:2]) up(i)*rot(i)*scale(1.2*i/24+1)]; // sweep(shape, concat(outside,inside)); -function sweep(shape, transformations, closed=false, caps) = - assert(is_list_of(transformations, ident(4)), "Input transformations must be a list of numeric 4x4 matrices in sweep") +function sweep(shape, transforms, closed=false, caps) = + assert(is_list_of(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep") assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.") let( caps = is_def(caps) ? caps : @@ -881,30 +881,30 @@ function sweep(shape, transformations, closed=false, caps) = capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])), fullcaps = is_bool(caps) ? [caps,caps] : caps ) - assert(len(transformations), "transformation must be length 2 or more") + assert(len(transforms), "transformation must be length 2 or more") assert(capsOK, "caps must be boolean or a list of two booleans") assert(!closed || !caps, "Cannot make closed shape with caps") is_region(shape)? let( regions = split_nested_region(shape), - rtrans = reverse(transformations), + rtrans = reverse(transforms), vnfs = [ for (rgn=regions) each [ for (path=select(rgn,0,-1)) - sweep(path, transformations, closed=closed, caps=false), + sweep(path, transforms, closed=closed, caps=false), if (fullcaps[0]) region_faces(rgn, reverse=true), - if (fullcaps[1]) region_faces(rgn, transform=select(transformations,-1)), + if (fullcaps[1]) region_faces(rgn, transform=select(transforms,-1)), ], ], vnf = vnf_merge(vnfs) ) vnf : assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points") - _skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps); + _skin_core([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],caps=fullcaps); -module sweep(shape, transformations, closed=false, caps, convexity=10, +module sweep(shape, transforms, closed=false, caps, convexity=10, anchor="origin",cp,spin=0, orient=UP, extent=false) { - vnf = sweep(shape, transformations, closed, caps); + vnf = sweep(shape, transforms, closed, caps); attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf)) { vnf_polyhedron(vnf,convexity=convexity); @@ -917,9 +917,9 @@ module sweep(shape, transformations, closed=false, caps, convexity=10, // Usage: // path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms]) // Description: -// Takes as input a 2d shape (specified as a point list) and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path. -// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true` then -// it returns a list of transformations suitable as input to `sweep`. +// Takes as input a 2D polygon path or region, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path. +// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true` +// then it returns a list of transformations suitable as input to `sweep`. // . // The sweep operation has an ambiguity: the shape can rotate around the axis defined by the path. Several options provide // methods for controlling this rotation. You can choose from three different methods for selecting the rotation of your shape. @@ -964,8 +964,8 @@ module sweep(shape, transformations, closed=false, caps, convexity=10, // If the model is closed then the twist must be a multiple of 360/symmetry. The twist is normally spread uniformly along your shape // based on the path length. If you set `twist_by_length` to false then the twist will be uniform based on the point count of your path. // Arguments: -// shape = a 2d path describing the shape to be swept -// path = 3d path giving the path to sweep over +// shape = A 2D polygon path or region describing the shape to be swept. +// path = 3D path giving the path to sweep over // method = one of "incremental", "natural" or "manual". Default: "incremental" // normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise. // closed = path is a closed loop. Default: false @@ -1214,7 +1214,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry)) assert(closed || symmetry==1, "symmetry must be 1 when closed is false") assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer") - assert(is_path(shape,2), "shape must be a 2d path") + assert(is_path(shape,2) || is_region(shape), "shape must be a 2d path or region.") assert(is_path(path), "input path is not a path") assert(!closed || !approx(path[0],select(path,-1)), "Closed path includes start point at the end") let( diff --git a/version.scad b/version.scad index 796f139..a6f9957 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,467]; +BOSL_VERSION = [2,0,468]; // Section: BOSL Library Version Functions From 90eaf73293e634f5ae8eb29af4d3f238956827de Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 17 Nov 2020 17:22:38 -0800 Subject: [PATCH 14/42] Bugfix for sweep() endcaps. --- skin.scad | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skin.scad b/skin.scad index ce50f9c..322bc1b 100644 --- a/skin.scad +++ b/skin.scad @@ -891,7 +891,7 @@ function sweep(shape, transforms, closed=false, caps) = for (rgn=regions) each [ for (path=select(rgn,0,-1)) sweep(path, transforms, closed=closed, caps=false), - if (fullcaps[0]) region_faces(rgn, reverse=true), + if (fullcaps[0]) region_faces(rgn, transform=transforms[0], reverse=true), if (fullcaps[1]) region_faces(rgn, transform=select(transforms,-1)), ], ], diff --git a/version.scad b/version.scad index a6f9957..189eb41 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,468]; +BOSL_VERSION = [2,0,469]; // Section: BOSL Library Version Functions From fa0281f2468db77c86c0e7aaf6c2693ca6eae868 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 29 Nov 2020 19:21:35 -0800 Subject: [PATCH 15/42] Added error checking to cuboid() --- shapes.scad | 16 +++++++++++----- version.scad | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/shapes.scad b/shapes.scad index c219731..9707599 100644 --- a/shapes.scad +++ b/shapes.scad @@ -81,7 +81,7 @@ module cuboid( c2 = vmul(corner,c/2); $fn = is_finite(chamfer)? 4 : segs(r); translate(vmul(corner, size/2-c)) { - if (cnt == 0) { + if (cnt == 0 || approx(r,0)) { translate(c2) cube(c, center=true); } else if (cnt == 1) { if (e.x) right(c2.x) xcyl(l=c.x, r=r); @@ -120,6 +120,12 @@ module cuboid( size = scalar_vec3(size); edges = edges(edges, except=except_edges); + assert(is_vector(size,3)); + assert(is_undef(chamfer) || is_finite(chamfer)); + assert(is_undef(rounding) || is_finite(rounding)); + assert(is_undef(p1) || is_vector(p1)); + assert(is_undef(p2) || is_vector(p2)); + assert(is_bool(trimcorners)); if (!is_undef(p1)) { if (!is_undef(p2)) { translate(pointlist_bounds([p1,p2])[0]) { @@ -131,19 +137,19 @@ module cuboid( } } } else { - if (chamfer != undef) { + if (is_finite(chamfer)) { if (any(edges[0])) assert(chamfer <= size.y/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube length or height."); if (any(edges[1])) assert(chamfer <= size.x/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube width or height."); if (any(edges[2])) assert(chamfer <= size.x/2 && chamfer <=size.y/2, "chamfer must be smaller than half the cube width or length."); } - if (rounding != undef) { + if (is_finite(rounding)) { if (any(edges[0])) assert(rounding <= size.y/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube length or height."); if (any(edges[1])) assert(rounding <= size.x/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube width or height."); if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length."); } majrots = [[0,90,0], [90,0,0], [0,0,0]]; attachable(anchor,spin,orient, size=size) { - if (chamfer != undef) { + if (is_finite(chamfer) && !approx(chamfer,0)) { if (edges == EDGES_ALL && trimcorners) { if (chamfer<0) { cube(size, center=true) { @@ -212,7 +218,7 @@ module cuboid( corner_shape([ 1, 1, 1]); } } - } else if (rounding != undef) { + } else if (is_finite(rounding) && !approx(rounding,0)) { sides = quantup(segs(rounding),4); if (edges == EDGES_ALL) { if(rounding<0) { diff --git a/version.scad b/version.scad index 189eb41..7b409d9 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,469]; +BOSL_VERSION = [2,0,470]; // Section: BOSL Library Version Functions From 0399cd085cbf9c6665fa1be85c79318153eeb5b3 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 29 Nov 2020 20:23:03 -0800 Subject: [PATCH 16/42] Guard various linear_extrude()s for height=0 --- beziers.scad | 6 ++++-- paths.scad | 14 +++++++++----- primitives.scad | 20 +++++++++++++------- shapes.scad | 26 ++++++++++++++++---------- shapes2d.scad | 4 ++-- version.scad | 2 +- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/beziers.scad b/beziers.scad index af9675e..23735c9 100644 --- a/beziers.scad +++ b/beziers.scad @@ -762,8 +762,10 @@ module linear_sweep_bezier(bezier, height=100, splinesteps=16, N=3, center, conv maxy = max([for (pt = bezier) abs(pt[1])]); anchor = get_anchor(anchor,center,BOT,BOT); attachable(anchor,spin,orient, size=[maxx*2,maxy*2,height]) { - linear_extrude(height=height, center=true, convexity=convexity, twist=twist, slices=slices, scale=scale) { - bezier_polygon(bezier, splinesteps=splinesteps, N=N); + if (height > 0) { + linear_extrude(height=height, center=true, convexity=convexity, twist=twist, slices=slices, scale=scale) { + bezier_polygon(bezier, splinesteps=splinesteps, N=N); + } } children(); } diff --git a/paths.scad b/paths.scad index ac95666..bdb05fb 100644 --- a/paths.scad +++ b/paths.scad @@ -779,12 +779,14 @@ module modulated_circle(r, sines=[10], d) // extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) { // xcopies(3) circle(3, $fn=32); // } -module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slices=undef) { +module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) { rtp = xyz_to_spherical(pt2-pt1); translate(pt1) { rotate([0, rtp[2], rtp[1]]) { - linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { - children(); + if (rtp[0] > 0) { + linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { + children(); + } } } } @@ -895,8 +897,10 @@ module path_extrude(path, convexity=10, clipsize=100) { translate(pt1) { Qrot(q) { down(clipsize/2/2) { - linear_extrude(height=dist+clipsize/2, convexity=convexity) { - children(); + if ((dist+clipsize/2) > 0) { + linear_extrude(height=dist+clipsize/2, convexity=convexity) { + children(); + } } } } diff --git a/primitives.scad b/primitives.scad index 8386094..98d6367 100644 --- a/primitives.scad +++ b/primitives.scad @@ -108,8 +108,10 @@ module cube(size=1, center, anchor, spin=0, orient=UP) anchor = get_anchor(anchor, center, ALLNEG, ALLNEG); size = scalar_vec3(size); attachable(anchor,spin,orient, size=size) { - linear_extrude(height=size.z, center=true, convexity=2) { - square([size.x,size.y], center=true); + if (size.z > 0) { + linear_extrude(height=size.z, center=true, convexity=2) { + square([size.x,size.y], center=true); + } } children(); } @@ -189,14 +191,18 @@ module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) l = first_defined([h, l, 1]); sides = segs(max(r1,r2)); attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { - if(r1>r2) { - linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) { - circle(r=r1); + if (r1 > r2) { + if (l > 0) { + linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) { + circle(r=r1); + } } } else { zflip() { - linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) { - circle(r=r2); + if (l > 0) { + linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) { + circle(r=r2); + } } } } diff --git a/shapes.scad b/shapes.scad index 9707599..0c63a81 100644 --- a/shapes.scad +++ b/shapes.scad @@ -512,8 +512,10 @@ module right_triangle(size=[1, 1, 1], center, anchor, spin=0, orient=UP) size = scalar_vec3(size); anchor = get_anchor(anchor, center, ALLNEG, ALLNEG); attachable(anchor,spin,orient, size=size) { - linear_extrude(height=size.z, convexity=2, center=true) { - polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]); + if (size.z > 0) { + linear_extrude(height=size.z, convexity=2, center=true) { + polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]); + } } children(); } @@ -1395,8 +1397,10 @@ module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, anchor= size = [r*2,l,r*2]; attachable(anchor,spin,orient, size=size) { rot(from=UP,to=FWD) { - linear_extrude(height=l, center=true, slices=2) { - teardrop2d(r=r, ang=ang, cap_h=cap_h); + if (l > 0) { + linear_extrude(height=l, center=true, slices=2) { + teardrop2d(r=r, ang=ang, cap_h=cap_h); + } } } children(); @@ -1567,12 +1571,14 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=FRONT+LEFT, spi steps = ceil(segs(r)*ang/360); step = ang/steps; attachable(anchor,spin,orient, size=[r,r,l]) { - linear_extrude(height=l, convexity=4, center=true) { - path = concat( - [[0,0]], - [for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]] - ); - translate(-[r,r]/2) polygon(path); + if (l > 0) { + linear_extrude(height=l, convexity=4, center=true) { + path = concat( + [[0,0]], + [for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]] + ); + translate(-[r,r]/2) polygon(path); + } } children(); } diff --git a/shapes2d.scad b/shapes2d.scad index 4eda015..3401250 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -298,7 +298,7 @@ module stroke( } } else { rotate([90,0,endcap_angle1]) { - linear_extrude(height=widths[0], center=true, convexity=convexity) { + linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) { polygon(endcap_shape1); } } @@ -318,7 +318,7 @@ module stroke( } } else { rotate([90,0,endcap_angle2]) { - linear_extrude(height=select(widths,-1), center=true, convexity=convexity) { + linear_extrude(height=max(select(widths,-1),0.001), center=true, convexity=convexity) { polygon(endcap_shape2); } } diff --git a/version.scad b/version.scad index 7b409d9..12852f3 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,470]; +BOSL_VERSION = [2,0,471]; // Section: BOSL Library Version Functions From d43b4667d56054a130c18397db41f02b847073d4 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 29 Nov 2020 22:39:42 -0800 Subject: [PATCH 17/42] Fix for #241: split_polygons_at_each_y produces non simple polygons --- geometry.scad | 27 +++++++++++++++------------ version.scad | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/geometry.scad b/geometry.scad index b18456f..62290a5 100644 --- a/geometry.scad +++ b/geometry.scad @@ -2005,10 +2005,11 @@ function _split_polygon_at_x(poly, x) = ], out1 = [for (p = poly2) if(p.x <= x) p], out2 = [for (p = poly2) if(p.x >= x) p], - out = [ - if (len(out1)>=3) out1, - if (len(out2)>=3) out2, - ] + out3 = [ + if (len(out1)>=3) each split_path_at_self_crossings(out1), + if (len(out2)>=3) each split_path_at_self_crossings(out2), + ], + out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] ) out; @@ -2034,10 +2035,11 @@ function _split_polygon_at_y(poly, y) = ], out1 = [for (p = poly2) if(p.y <= y) p], out2 = [for (p = poly2) if(p.y >= y) p], - out = [ - if (len(out1)>=3) out1, - if (len(out2)>=3) out2, - ] + out3 = [ + if (len(out1)>=3) each split_path_at_self_crossings(out1), + if (len(out2)>=3) each split_path_at_self_crossings(out2), + ], + out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] ) out; @@ -2063,10 +2065,11 @@ function _split_polygon_at_z(poly, z) = ], out1 = [for (p = poly2) if(p.z <= z) p], out2 = [for (p = poly2) if(p.z >= z) p], - out = [ - if (len(out1)>=3) out1, - if (len(out2)>=3) out2, - ] + out3 = [ + if (len(out1)>=3) each split_path_at_self_crossings(close_path(out1), closed=false), + if (len(out2)>=3) each split_path_at_self_crossings(close_path(out2), closed=false), + ], + out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] ) out; diff --git a/version.scad b/version.scad index 12852f3..be986f3 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,471]; +BOSL_VERSION = [2,0,472]; // Section: BOSL Library Version Functions From f36fbb60dbf85665fd573e28b0f4f9c3ce0274a0 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 30 Nov 2020 21:09:00 -0800 Subject: [PATCH 18/42] Fix for #166: tweaks for xcopies, etc. --- distributors.scad | 9 ++-- tests/test_transforms.scad | 9 ++++ transforms.scad | 86 +++++++++++++++++++++++++++++--------- version.scad | 2 +- 4 files changed, 83 insertions(+), 23 deletions(-) diff --git a/distributors.scad b/distributors.scad index 3d10111..ff3482e 100644 --- a/distributors.scad +++ b/distributors.scad @@ -152,7 +152,7 @@ module line_of(spacing, n, l, p1, p2) // spacing = spacing between copies. (Default: 1.0) // n = Number of copies to spread out. (Default: 2) // l = Length to spread copies over. -// sp = If given, copies will be spread on a line to the right of starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0]. +// sp = If given as a point, copies will be spread on a line to the right of starting position `sp`. If given as a scalar, copies will be spread on a line to the right of starting position `[sp,0,0]`. If not given, copies will be spread along a line that is centered at [0,0,0]. // // Side Effects: // `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. @@ -170,6 +170,7 @@ module line_of(spacing, n, l, p1, p2) // } module xcopies(spacing, n, l, sp) { + sp = is_finite(sp)? [sp,0,0] : sp; line_of(l=l*RIGHT, spacing=spacing*RIGHT, n=n, p1=sp) children(); } @@ -187,7 +188,7 @@ module xcopies(spacing, n, l, sp) // spacing = spacing between copies. (Default: 1.0) // n = Number of copies to spread out. (Default: 2) // l = Length to spread copies over. -// sp = If given, copies will be spread on a line back from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0]. +// sp = If given as a point, copies will be spread on a line back from starting position `sp`. If given as a scalar, copies will be spread on a line back from starting position `[0,sp,0]`. If not given, copies will be spread along a line that is centered at [0,0,0]. // // Side Effects: // `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. @@ -205,6 +206,7 @@ module xcopies(spacing, n, l, sp) // } module ycopies(spacing, n, l, sp) { + sp = is_finite(sp)? [0,sp,0] : sp; line_of(l=l*BACK, spacing=spacing*BACK, n=n, p1=sp) children(); } @@ -222,7 +224,7 @@ module ycopies(spacing, n, l, sp) // spacing = spacing between copies. (Default: 1.0) // n = Number of copies to spread out. (Default: 2) // l = Length to spread copies over. -// sp = If given, copies will be spread on a line up from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0]. +// sp = If given as a point, copies will be spread on a line up from starting position `sp`. If given as a scalar, copies will be spread on a line up from starting position `[0,0,sp]`. If not given, copies will be spread along a line that is centered at [0,0,0]. // // Side Effects: // `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. @@ -240,6 +242,7 @@ module ycopies(spacing, n, l, sp) // } module zcopies(spacing, n, l, sp) { + sp = is_finite(sp)? [0,0,sp] : sp; line_of(l=l*UP, spacing=spacing*UP, n=n, p1=sp) children(); } diff --git a/tests/test_transforms.scad b/tests/test_transforms.scad index 3fb044b..2681b48 100644 --- a/tests/test_transforms.scad +++ b/tests/test_transforms.scad @@ -106,14 +106,23 @@ test_up(); module test_scale() { + cb = cube(1); vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]]; for (val=vals) { + assert_equal(scale(point2d(val)), [[val.x,0,0],[0,val.y,0],[0,0,1]]); assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]); assert_equal(scale(val, p=[1,2,3]), vmul([1,2,3], val)); scale(val) nil(); } assert_equal(scale(3), [[3,0,0,0],[0,3,0,0],[0,0,3,0],[0,0,0,1]]); assert_equal(scale(3, p=[1,2,3]), 3*[1,2,3]); + assert_equal(scale(3, p=cb), cube(3)); + assert_equal(scale(2, p=square(1)), square(2)); + assert_equal(scale(2, cp=[1,1], p=square(1)), square(2, center=true)); + assert_equal(scale([2,3], p=square(1)), square([2,3])); + assert_equal(scale([2,2], cp=[0.5,0.5], p=square(1)), move([-0.5,-0.5], p=square([2,2]))); + assert_equal(scale([2,3,4], p=cb), cube([2,3,4])); + assert_equal(scale([-2,-3,-4], p=cb), [[for (p=cb[0]) vmul(p,[-2,-3,-4])], [for (f=cb[1]) reverse(f)]]); // Verify that module at least doesn't crash. scale(-5) scale(5) nil(); } diff --git a/transforms.scad b/transforms.scad index 43cb257..1be4514 100644 --- a/transforms.scad +++ b/transforms.scad @@ -524,12 +524,12 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p); // Function&Module: scale() // Usage: As Module -// scale(SCALAR) ... -// scale([X,Y,Z]) ... +// scale(SCALAR, ) ... +// scale([X,Y,Z], ) ... // Usage: Scale Points -// pts = scale(v, p); +// pts = scale(v, p, ); // Usage: Get Scaling Matrix -// mat = scale(v); +// mat = scale(v, ); // Description: // Scales by the [X,Y,Z] scaling factors given in `v`. If `v` is given as a scalar number, all axes are scaled uniformly by that amount. // * Called as the built-in module, scales all children. @@ -541,6 +541,7 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p); // * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix. // Arguments: // v = Either a numeric uniform scaling factor, or a list of [X,Y,Z] scaling factors. Default: 1 +// cp = If given, centers the scaling on the point `cp`. // p = If called as a function, the point or list of points to scale. // Example(NORENDER): // pt1 = scale(3, p=[3,1,4]); // Returns: [9,3,12] @@ -552,20 +553,33 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p); // path = circle(d=50,$fn=12); // #stroke(path,closed=true); // stroke(scale([1.5,3],p=path),closed=true); -function scale(v=1, p=undef) = +function scale(v=1, cp=[0,0,0], p=undef) = assert(is_num(v) || is_vector(v)) assert(is_undef(p) || is_list(p)) - let(v = is_num(v)? [v,v,v] : v) + let( v = is_num(v)? [v,v,v] : v ) is_undef(p)? ( - len(v)==2? affine2d_scale(v) : affine3d_scale(point3d(v)) + len(v)==2? ( + cp==[0,0,0] || cp == [0,0] ? affine2d_scale(v) : ( + affine2d_translate(point2d(cp)) * + affine2d_scale(v) * + affine2d_translate(point2d(-cp)) + ) + ) : ( + cp==[0,0,0] ? affine3d_scale(v) : ( + affine3d_translate(point3d(cp)) * + affine3d_scale(v) * + affine3d_translate(point3d(-cp)) + ) + ) ) : ( assert(is_list(p)) - is_vector(p)? ( len(p)==2? vmul(p,point2d(v)) : vmul(p,point3d(v,1)) ) : + let( mat = scale(v=v, cp=cp) ) + is_vector(p)? apply(mat, p) : is_vnf(p)? let(inv=product([for (x=v) x<0? -1 : 1])) [ - scale(v=v, p=p[0]), + apply(mat, p[0]), inv>=0? p[1] : [for (l=p[1]) reverse(l)] ] : - [ for (pp=p) scale(v=v, p=pp) ] + apply(mat, p) ); @@ -591,7 +605,8 @@ function scale(v=1, p=undef) = // // Arguments: // x = Factor to scale by, along the X axis. -// p = A point or path to scale, when called as a function. +// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[cp,0,0]` +// p = A point, path, bezier patch, or VNF to scale, when called as a function. // planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix. // // Example: As Module @@ -601,9 +616,20 @@ function scale(v=1, p=undef) = // path = circle(d=50,$fn=12); // #stroke(path,closed=true); // stroke(xscale(2,p=path),closed=true); -module xscale(x=1) scale([x,1,1]) children(); +module xscale(x=1, cp=0) { + cp = is_num(cp)? [cp,0,0] : cp; + if (cp == [0,0,0]) { + scale([x,1,1]) children(); + } else { + translate(cp) scale([x,1,1]) translate(-cp) children(); + } +} -function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)==2))? scale([x,1],p=p) : scale([x,1,1],p=p); +function xscale(x=1, cp=0, p, planar=false) = + let( cp = is_num(cp)? [cp,0,0] : cp ) + (planar || (!is_undef(p) && len(p)==2)) + ? scale([x,1], cp=cp, p=p) + : scale([x,1,1], cp=cp, p=p); // Function&Module: yscale() @@ -627,7 +653,8 @@ function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p) // // Arguments: // y = Factor to scale by, along the Y axis. -// p = A point or path to scale, when called as a function. +// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[0,cp,0]` +// p = A point, path, bezier patch, or VNF to scale, when called as a function. // planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix. // // Example: As Module @@ -637,9 +664,20 @@ function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p) // path = circle(d=50,$fn=12); // #stroke(path,closed=true); // stroke(yscale(2,p=path),closed=true); -module yscale(y=1) scale([1,y,1]) children(); +module yscale(y=1, cp=0) { + cp = is_num(cp)? [0,cp,0] : cp; + if (cp == [0,0,0]) { + scale([1,y,1]) children(); + } else { + translate(cp) scale([1,y,1]) translate(-cp) children(); + } +} -function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)==2))? scale([1,y],p=p) : scale([1,y,1],p=p); +function yscale(y=1, cp=0, p, planar=false) = + let( cp = is_num(cp)? [0,cp,0] : cp ) + (planar || (!is_undef(p) && len(p)==2)) + ? scale([1,y],p=p) + : scale([1,y,1],p=p); // Function&Module: zscale() @@ -663,7 +701,8 @@ function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p) // // Arguments: // z = Factor to scale by, along the Z axis. -// p = A point or path to scale, when called as a function. +// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[0,0,cp]` +// p = A point, path, bezier patch, or VNF to scale, when called as a function. // planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix. // // Example: As Module @@ -673,9 +712,18 @@ function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p) // path = xrot(90,p=path3d(circle(d=50,$fn=12))); // #trace_path(path); // trace_path(zscale(2,p=path)); -module zscale(z=1) scale([1,1,z]) children(); +module zscale(z=1, cp=0) { + cp = is_num(cp)? [0,0,cp] : cp; + if (cp == [0,0,0]) { + scale([1,1,z]) children(); + } else { + translate(cp) scale([1,1,z]) translate(-cp) children(); + } +} -function zscale(z=1, p=undef) = scale([1,1,z],p=p); +function zscale(z=1, cp=0, p) = + let( cp = is_num(cp)? [0,0,cp] : cp ) + scale([1,1,z], cp=cp, p=p); // Function&Module: mirror() diff --git a/version.scad b/version.scad index be986f3..1744eeb 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,472]; +BOSL_VERSION = [2,0,473]; // Section: BOSL Library Version Functions From 0df1308e4edf49f4ff1d32c9d61d8c95bb4bcadf Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Tue, 1 Dec 2020 23:56:57 +0100 Subject: [PATCH 19/42] fixed modulated_circle() to work as advertised --- CONTRIBUTING.md | 2 +- paths.scad | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8595280..199859b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ If you wish to contribute bugfixes or code to the BOSL2 project, the standard wa 3. Click the Clone button. 4. When it asks "How are you planning to use this fork?", click on the button "To contribute to the parent project." -1. Before you edit files, always syncronize with the upstream repository: +1. Before you edit files, always synchronize with the upstream repository: - If using the command-line: ``` git pull upstream diff --git a/paths.scad b/paths.scad index bdb05fb..fe14a50 100644 --- a/paths.scad +++ b/paths.scad @@ -747,16 +747,20 @@ function assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) = // Arguments: // r = Radius of the base circle. Default: 40 // d = Diameter of the base circle. -// sines = array of [amplitude, frequency] pairs, where the frequency is the number of times the cycle repeats around the circle. +// sines = array of [amplitude, frequency] pairs or [amplitude, frequency, phase] triples, where the frequency is the number of times the cycle repeats around the circle. // Example(2D): // modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); -module modulated_circle(r, sines=[10], d) +module modulated_circle(r, sines=[[1,1]], d) { r = get_radius(r=r, d=d, dflt=40); - freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; + assert(is_list(sines) + && all([for(s=sines) is_vector(s,2) || is_vector(s,3)]), + "sines must be given as a list of pairs or triples"); + sines_ = [for(s=sines) [s[0], s[1], len(s)==2 ? 0 : s[2]]]; + freqs = len(sines_)>0? [for (i=sines_) i[1]] : [5]; points = [ for (a = [0 : (360/segs(r)/max(freqs)) : 360]) - let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)] + let(nr=r+sum_of_sines(a,sines_)) [nr*cos(a), nr*sin(a)] ]; polygon(points); } From 579a3e5c99003589ec0697fb029e74b5bbd04f5f Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 2 Dec 2020 18:15:05 -0500 Subject: [PATCH 20/42] Fixed trapezohedron bug --- polyhedra.scad | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/polyhedra.scad b/polyhedra.scad index bdcd87e..a4cf00e 100644 --- a/polyhedra.scad +++ b/polyhedra.scad @@ -283,7 +283,7 @@ module regular_polyhedron( faces=undef, facetype=undef, hasfaces=undef, - side=1, + side=undef, ir=undef, mr=undef, or=undef, @@ -591,7 +591,7 @@ function regular_polyhedron_info( info=undef, name=undef, index=undef, type=undef, faces=undef, facetype=undef, - hasfaces=undef, side=1, + hasfaces=undef, side=undef, ir=undef, mr=undef, or=undef, r=undef, d=undef, anchor=[0,0,0], center=undef, @@ -602,7 +602,7 @@ function regular_polyhedron_info( argcount = num_defined([ir,mr,or,r,d]) ) assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'") - let( + let( ////////////////////// //Index values into the _polyhedra_ array // @@ -664,6 +664,7 @@ function regular_polyhedron_info( ) assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices])) let( + side = default(side,1), // This default setting must occur after _trapezohedron is called scalefactor = ( name=="trapezohedron" ? 1 : ( argcount == 0? side : @@ -730,7 +731,7 @@ function _stellate_faces(scalefactor,stellate,vertices,faces_normals) = function _trapezohedron(faces, r, side, longside, h, d) = assert(faces%2==0, "Must set 'faces' to an even number for trapezohedron") let( - r = get_radius(r=r, d=d, dflt=1), + r = get_radius(r=r, d=d), N = faces/2, parmcount = num_defined([r,side,longside,h]) ) From ae61174a87481e4e9bdb2735c0add6b2468adb6b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 2 Dec 2020 18:25:13 -0500 Subject: [PATCH 21/42] fix test for yscale change --- tests/test_math.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_math.scad b/tests/test_math.scad index 1d9fee9..56f3ae2 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -889,7 +889,7 @@ module test_deriv(){ [0.469846310393,-0.813797681349], [0.925416578398,0.163175911167], [0.696902572292,1.45914323952]]); - spent = yscale(8,pent); + spent = yscale(8,p=pent); lens = path_segment_lengths(spent,closed=true); assert_approx(deriv(spent, closed=true, h=lens), [[-0.0381285841663,0.998065839726], From d47fbef5a700a3cda061e6d8e7354b563fcbb7dc Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 01:01:39 +0100 Subject: [PATCH 22/42] half_of() coded as a function for points, paths and regions; VNFs still TODO --- mutators.scad | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/mutators.scad b/mutators.scad index a5a4634..cca657f 100644 --- a/mutators.scad +++ b/mutators.scad @@ -64,13 +64,20 @@ module bounding_box(excess=0) { } -// Module: half_of() +// Function&Module: half_of() // -// Usage: +// Usage: as module // half_of(v, [cp], [s]) ... +// Usage: as function +// half_of(v, [cp], p, [s])... // // Description: // Slices an object at a cut plane, and masks away everything that is on one side. +// * Called as a function with a path in the `p` argument, returns the +// intersection of path `p` and given half-space. +// * Called as a function with a 2D path in the `p` argument +// and a 2D vector `p`, returns the intersection of path `p` and given +// half-plane. // // Arguments: // v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP) @@ -111,6 +118,54 @@ module half_of(v=UP, cp, s=1000, planar=false) } } +function half_of(v, arg1, arg2, cp, p, s=1e4) = + /* may be called as either: + * p= cp= + * 1. (v, p) arg1 0 + * 2. (v, p=p) p 0 + * 3. (v, cp, p) arg2 arg1 + * 4. (v, cp=cp, p) arg1 p + * 5. (v, cp, p=p) p arg1 + * 6. (v, cp=cp, p=p)p cp + */ + /* FIXME: add tests for the various argument naming schemes */ + let(p_=p, cp_=cp, // keep names p and cp clean + p = !is_undef(p_) ? p_ : // cases 2.5.6. + !is_undef(arg2) ? arg2 : arg1, // cases 3., 1.4. + cp0=!is_undef(cp_) ? cp_ : // cases 4.6. + is_undef(arg1) ? 0*v : // case 2. + !is_undef(arg2) ? arg1 : // case 3. + is_undef(p_) ? 0*v : arg1, // cases 1., 5. + cp = is_num(cp0) ? cp0*unit(v) : cp0) + assert(is_vector(v,2)||is_vector(v,3), + "must provide a half-plane or half-space") + let(d=len(v)) + assert(len(cp) == d, str("cp must have dimension ", d)) + is_vector(p) ? + assert(len(p) == d, str("vector must have dimension ", d)) + let(z=(p-cp)*v) (z >= 0 ? p : p - (z*v)/(v*v)) + : + p == [] ? [] : // special case: empty path remains empty + is_path(p) ? + assert(len(p[0]) == d, str("path must have dimension ", d)) + let(z = [for(x=p) (x-cp)*v]) + [ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [], + // we assume a closed path here; + // to make this correct for an open path, + // just replace this by [] when i==len(p)-1: + let(j=(i+1)%len(p)) + // the remaining path may have flattened sections, but this cannot + // create self-intersection or whiskers: + z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] + : + assert(is_region(p), str("must provide point, path or region")) + assert(len(v) == 2, str("3D vector not compatible with region")) + let(u=unit(v), w=[-u[1], u[0]], + R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // bounding region + intersection(R, p); + // FIXME: find something intelligent to do if p is a VNF + // FIXME: scadlib csg.scad, csg_hspace() + // Module: left_half() // From e6ef5e98be39e17c6b6286faaac6c6ca49e3b4de Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 2 Dec 2020 17:21:58 -0800 Subject: [PATCH 23/42] Fix math.scad regression. --- tests/test_math.scad | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_math.scad b/tests/test_math.scad index 1d9fee9..56f3ae2 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -889,7 +889,7 @@ module test_deriv(){ [0.469846310393,-0.813797681349], [0.925416578398,0.163175911167], [0.696902572292,1.45914323952]]); - spent = yscale(8,pent); + spent = yscale(8,p=pent); lens = path_segment_lengths(spent,closed=true); assert_approx(deriv(spent, closed=true, h=lens), [[-0.0381285841663,0.998065839726], diff --git a/version.scad b/version.scad index 1744eeb..dfc5f4f 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,473]; +BOSL_VERSION = [2,0,474]; // Section: BOSL Library Version Functions From 91bca08f5041552784868f15cc4258da875d7770 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 17:45:20 +0100 Subject: [PATCH 24/42] Allowed half_of() to work on paths, regions and surfaces. --- common.scad | 46 +++++++++++++++++++ mutators.scad | 19 ++++---- vnf.scad | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/common.scad b/common.scad index 0a08f5d..24b181a 100644 --- a/common.scad +++ b/common.scad @@ -312,7 +312,53 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); +// Function: get_named_args(anonymous, named, _undef) +// Usage: +// function f(anon1=_undef, anon2=_undef,..., +// named1=_undef, named2=_undef, ...) = +// let(args = get_named_args([anon1, anon2, ...], +// [[named1, default1], [named2, default2], ...])) +// ... +// Description: +// given a set of anonymous and named arguments, returns the values of +// named arguments, in order. +// - All named arguments which were provided by the user take the +// value provided. +// - All named arguments which were not provided by the user are +// affected from anonymous arguments, in order. +// - Any remaining named arguments take the provided default values. +// Arguments: +// anonymous = the list of values of anonymous arguments. +// named = the list of [passed-value, default value] of named arguments. +// _undef = the default value used by the calling function for all +// arguments (this is *not* undef, or any value that the user might +// purposely want to use as an argument value). +// Examples: +// function f(arg1=_undef, arg2=_undef, arg3=_undef, +// named1=_undef, named2=_undef, named3=_undef) = +// let(named = get_named_args([arg1, arg2, arg3], +// [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) +// named; +// echo(f()); // ["default1", "default2", "default3"] +// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] +// echo(f("given1")); // ["given1", "default2", "default3"] +// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] +// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// a value that the user should never enter randomly; +// result of `dd if=/dev/random bs=32 count=1 |base64` : +_undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; + +function get_named_args(anonymous, named,_undef=_undef) = + /* u: set of undefined indices in named arguments */ + let(from_anon = [for(p=enumerate(named)) if(p[1][0]==_undef) p[0]], + n = len(anonymous)) + echo("from_anon:", from_anon) + [ for(e = enumerate(named)) + // if the value is defined, return it: + e[1][0] != _undef ? e[1][0] : + let(k = anonymous[search(e[0], from_anon)[0]]) + k != _undef ? k : e[1][1] ]; // Function: scalar_vec3() // Usage: // scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index cca657f..49115f4 100644 --- a/mutators.scad +++ b/mutators.scad @@ -118,7 +118,7 @@ module half_of(v=UP, cp, s=1000, planar=false) } } -function half_of(v, arg1, arg2, cp, p, s=1e4) = +function half_of(v, arg1, arg2, s=1e4, cp, p) = /* may be called as either: * p= cp= * 1. (v, p) arg1 0 @@ -158,14 +158,15 @@ function half_of(v, arg1, arg2, cp, p, s=1e4) = // create self-intersection or whiskers: z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] : - assert(is_region(p), str("must provide point, path or region")) - assert(len(v) == 2, str("3D vector not compatible with region")) - let(u=unit(v), w=[-u[1], u[0]], - R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // bounding region - intersection(R, p); - // FIXME: find something intelligent to do if p is a VNF - // FIXME: scadlib csg.scad, csg_hspace() - + is_region(p) ? + assert(len(v) == 2, str("3D vector not compatible with region")) + let(u=unit(v), w=[-u[1], u[0]], + R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // half-plane + intersection(R, p) + : + is_vnf(p) ? + vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : + assert(false, "must pass either a point, a path, a region, or a VNF"); // Module: left_half() // diff --git a/vnf.scad b/vnf.scad index bd5121a..8b40841 100644 --- a/vnf.scad +++ b/vnf.scad @@ -827,5 +827,130 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { color([0.5,0.5,0.5,0.5]) vnf_polyhedron(vnf); } +// Section: VNF transformations +// +// Function: vnf_halfspace(halfspace, vnf) +// Usage: +// vnf_halfspace([a,b,c,d], vnf) +// Description: +// returns the intersection of the VNF with the given half-space. +// Arguments: +// halfspace = half-space to intersect with, given as the four +// coefficients of the affine inequation a*x+b*y+c*z+d ≥ 0. +function _vnf_halfspace_pts(halfspace, points, faces, + inside=undef, coords=[], map=[]) = +/* Recursive function to compute the intersection of points (and edges, + * but not faces) with with the half-space. + * Parameters: + * halfspace a vector(4) + * points a list of points3d + * faces a list of indexes in points + * inside a vector{bool} determining which points belong to the + * half-space; if undef, it is initialized at first loop. + * coords the coordinates of the points in the intersection + * map the logical map (old point) → (new point(s)): + * if point i is kept, then map[i] = new-index-for-i; + * if point i is dropped, then map[i] = [[j1, k1], [j2, k2], …], + * where points j1,… are kept (old index) + * and k1,… are the matching intersections (new index). + * Returns the triple [coords, map, inside]. + * + */ + let(i=len(map), n=len(coords)) // we are currently processing point i + // termination test: + i >= len(points) ? [ coords, map, inside ] : + let(inside = !is_undef(inside) ? inside : + [for(x=points) halfspace*concat(x,[1]) >= 0], + pi = points[i]) + // inside half-space: keep the point (and reindex) + inside[i] ? _vnf_halfspace_pts(halfspace, points, faces, inside, + concat(coords, [pi]), concat(map, [n])) + : // else: compute adjacent vertices (adj) + let(adj = unique([for(f=faces) let(m=len(f), j=search(i, f)[0]) + each if(j!=undef) [f[(j+1)%m], f[(j+m-1)%m]] ]), + // filter those which lie in half-space: + adj2 = [for(x=adj) if(inside[x]) x], + zi = halfspace*concat(pi, [1])) + _vnf_halfspace_pts(halfspace, points, faces, inside, + // new points: we append all these intersection points + concat(coords, [for(j=adj2) let(zj=halfspace*concat(points[j],[1])) + (zi*points[j]-zj*pi)/(zi-zj)]), + // map: we add the info + concat(map, [[for(y=enumerate(adj2)) [y[1], n+y[0]]]])); +function _vnf_halfspace_face(face, map, inside, i=0, + newface=[], newedge=[], exit) = +/* Recursive function to intersect a face of the VNF with the half-plane. + * Arguments: + * face: the list of points of the face (old indices). + * map: as produced by _vnf_halfspace_pts + * inside: vector{bool} containing half-space info + * i: index for iteration + * exit: boolean; is first point in newedge an exit or an entrance from + * half-space? + * newface: list of (new indexes of) points on the face + * newedge: list of new points on the plane (even number of points) + * Return value: [newface, new-edges], where new-edges is a list of + * pairs [entrance-node, exit-node] (new indices). + */ +// termination condition: + (i >= len(face)) ? [ newface, + // if exit==true then we return newedge[1,0], newedge[3,2], ... + // otherwise newedge[0,1], newedge[2,3], ...; + // all edges are oriented (entrance->exit), so that by following the + // arrows we obtain a correctly-oriented face: + let(k = exit ? 0 : 1) + [for(i=[0:2:len(newedge)-2]) [newedge[i+k], newedge[i+1-k]]] ] + : // recursion case: p is current point on face, q is next point + let(p = face[i], q = face[(i+1)%len(face)], + // if p is inside half-plane, keep it in the new face: + newface0 = inside[p] ? concat(newface, [map[p]]) : newface) + // if the current segment does not intersect, this is all: + inside[p] == inside[q] ? _vnf_halfspace_face(face, map, inside, i+1, + newface0, newedge, exit) + : // otherwise, we must add the intersection point: + // rename the two points p,q as inner and outer point: + let(in = inside[p] ? p : q, out = p+q-in, + inter=[for(a=map[out]) if(a[0]==in) a[1]][0]) + _vnf_halfspace_face(face, map, inside, i+1, + concat(newface0, [inter]), + concat(newedge, [inter]), + is_undef(exit) ? inside[p] : exit); +function _vnf_halfspace_paths(edges, i=0, paths=[]) = +/* given a set of oriented edges [x,y], + returns all paths [x,y,z,..] that may be formed from these edges. + A closed path will be returned with equal first and last point. + i: index of currently examined edge + */ + i >= len(edges) ? paths : // termination condition + let(e = edges[i], + s = [for(x=enumerate(paths)) if(x[1][len(x[1])-1] == e[0]) x[0]]) + _vnf_halfspace_paths(edges, i+1, + // if cannot attach to previous path: create a new one + s == [] ? concat(paths, [e]) : + // otherwise, attach to found path + [for(x=enumerate(paths)) x[0]==s[0] ? concat(x[1], [e[1]]) : x[1]]); +function vnf_halfspace(_arg1=_undef, _arg2=_undef, + halfspace=_undef, vnf=_undef) = + // here is where we wish that OpenSCAD had array lvalues... + let(args=get_named_args([_arg1, _arg2], [[halfspace],[vnf]]), + halfspace=args[0], vnf=args[1]) + assert(is_vector(halfspace, 4), + "half-space must be passed as a length 4 affine form") + assert(is_vnf(vnf), "must pass a vnf") + // read points + let(tmp1=_vnf_halfspace_pts(halfspace, vnf[0], vnf[1]), + coords=tmp1[0], map=tmp1[1], inside=tmp1[2], + // cut faces and generate edges + tmp2= [for(f=vnf[1]) _vnf_halfspace_face(f, map, inside)], + newfaces=[for(x=tmp2) if(x[0]!=[]) x[0]], + newedges=[for(x=tmp2) each x[1]], + // generate new faces + paths=_vnf_halfspace_paths(newedges), + loops=[for(p=paths) if(p[0] == p[len(p)-1]) p]) + [coords, concat(newfaces, loops)]; + +// +// // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap +// From 22d853852dba59def7b71e45699645afc814ecd4 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 18:00:31 +0100 Subject: [PATCH 25/42] Improved a bit on is_region(); use get_named_args() in half_of() --- mutators.scad | 36 ++++++++++++++++-------------------- regions.scad | 3 ++- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/mutators.scad b/mutators.scad index 49115f4..8c4bc96 100644 --- a/mutators.scad +++ b/mutators.scad @@ -118,25 +118,19 @@ module half_of(v=UP, cp, s=1000, planar=false) } } -function half_of(v, arg1, arg2, s=1e4, cp, p) = - /* may be called as either: - * p= cp= - * 1. (v, p) arg1 0 - * 2. (v, p=p) p 0 - * 3. (v, cp, p) arg2 arg1 - * 4. (v, cp=cp, p) arg1 p - * 5. (v, cp, p=p) p arg1 - * 6. (v, cp=cp, p=p)p cp - */ - /* FIXME: add tests for the various argument naming schemes */ - let(p_=p, cp_=cp, // keep names p and cp clean - p = !is_undef(p_) ? p_ : // cases 2.5.6. - !is_undef(arg2) ? arg2 : arg1, // cases 3., 1.4. - cp0=!is_undef(cp_) ? cp_ : // cases 4.6. - is_undef(arg1) ? 0*v : // case 2. - !is_undef(arg2) ? arg1 : // case 3. - is_undef(p_) ? 0*v : arg1, // cases 1., 5. +function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, + v=_undef, cp=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3, _arg4], + [[v], [cp, 0], [p], [s, 1e4]]), + v=args[0], cp0=args[1], p=args[2], s=args[3], cp = is_num(cp0) ? cp0*unit(v) : cp0) + echo("_undef=", _undef) + echo("v=", v) + echo("cp=", cp) + echo("p=", p) + echo("vnf?", is_vnf(p)) + echo("region?", is_region(p)) + echo("s=", s) assert(is_vector(v,2)||is_vector(v,3), "must provide a half-plane or half-space") let(d=len(v)) @@ -158,14 +152,16 @@ function half_of(v, arg1, arg2, s=1e4, cp, p) = // create self-intersection or whiskers: z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] : + is_vnf(p) ? + // we must put is_vnf() before is_region(), because most triangulated + // VNFs will pass is_region() test + vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : is_region(p) ? assert(len(v) == 2, str("3D vector not compatible with region")) let(u=unit(v), w=[-u[1], u[0]], R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // half-plane intersection(R, p) : - is_vnf(p) ? - vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : assert(false, "must pass either a point, a path, a region, or a VNF"); // Module: left_half() diff --git a/regions.scad b/regions.scad index acd66d9..8923751 100644 --- a/regions.scad +++ b/regions.scad @@ -20,7 +20,8 @@ // is_region(x); // Description: // Returns true if the given item looks like a region. A region is defined as a list of zero or more paths. -function is_region(x) = is_list(x) && is_path(x.x); +function is_region(x) = + is_list(x) && all([for(y=x) is_path(y, len(x[0][0]))]); // Function: close_region() From 12a8388ae69c11b7786e8e2ff418cfa1139ea7c6 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 2 Dec 2020 17:21:58 -0800 Subject: [PATCH 26/42] Fix math.scad regression. --- tests/test_math.scad | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_math.scad b/tests/test_math.scad index 1d9fee9..56f3ae2 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -889,7 +889,7 @@ module test_deriv(){ [0.469846310393,-0.813797681349], [0.925416578398,0.163175911167], [0.696902572292,1.45914323952]]); - spent = yscale(8,pent); + spent = yscale(8,p=pent); lens = path_segment_lengths(spent,closed=true); assert_approx(deriv(spent, closed=true, h=lens), [[-0.0381285841663,0.998065839726], diff --git a/version.scad b/version.scad index 1744eeb..dfc5f4f 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,473]; +BOSL_VERSION = [2,0,474]; // Section: BOSL Library Version Functions From 8dff77b8bc209a5ab9deda80f5670687620257c4 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 2 Dec 2020 18:15:05 -0500 Subject: [PATCH 27/42] Fixed trapezohedron bug --- polyhedra.scad | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/polyhedra.scad b/polyhedra.scad index bdcd87e..a4cf00e 100644 --- a/polyhedra.scad +++ b/polyhedra.scad @@ -283,7 +283,7 @@ module regular_polyhedron( faces=undef, facetype=undef, hasfaces=undef, - side=1, + side=undef, ir=undef, mr=undef, or=undef, @@ -591,7 +591,7 @@ function regular_polyhedron_info( info=undef, name=undef, index=undef, type=undef, faces=undef, facetype=undef, - hasfaces=undef, side=1, + hasfaces=undef, side=undef, ir=undef, mr=undef, or=undef, r=undef, d=undef, anchor=[0,0,0], center=undef, @@ -602,7 +602,7 @@ function regular_polyhedron_info( argcount = num_defined([ir,mr,or,r,d]) ) assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'") - let( + let( ////////////////////// //Index values into the _polyhedra_ array // @@ -664,6 +664,7 @@ function regular_polyhedron_info( ) assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices])) let( + side = default(side,1), // This default setting must occur after _trapezohedron is called scalefactor = ( name=="trapezohedron" ? 1 : ( argcount == 0? side : @@ -730,7 +731,7 @@ function _stellate_faces(scalefactor,stellate,vertices,faces_normals) = function _trapezohedron(faces, r, side, longside, h, d) = assert(faces%2==0, "Must set 'faces' to an even number for trapezohedron") let( - r = get_radius(r=r, d=d, dflt=1), + r = get_radius(r=r, d=d), N = faces/2, parmcount = num_defined([r,side,longside,h]) ) From c7c982710fbf370f17c3dbc6cf906b9b8c6ca1d0 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:21:05 +0100 Subject: [PATCH 28/42] Added more flexibility to get_named_args(), and all the left_half(), etc. as functions --- arrays.scad | 12 +++++++++ common.scad | 73 +++++++++++++++++++++++++++++++++++---------------- mutators.scad | 63 +++++++++++++++++++++++++++++++++----------- vnf.scad | 4 +-- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/arrays.scad b/arrays.scad index e23ef7b..967109b 100644 --- a/arrays.scad +++ b/arrays.scad @@ -89,6 +89,18 @@ function select(list, start, end=undef) = : concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ; +// Function: last() +// Description: +// Returns the last element of a list, or undef if empty. +// Usage: +// last(list) +// Arguments: +// list = The list to get the last element of. +// Example: +// l = [3,4,5,6,7,8,9]; +// last(l); // Returns 9. +function last(list) = list[len(list)-1]; + // Function: slice() // Description: // Returns a slice of a list. The first item is index 0. diff --git a/common.scad b/common.scad index 24b181a..a0abd81 100644 --- a/common.scad +++ b/common.scad @@ -312,27 +312,41 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); -// Function: get_named_args(anonymous, named, _undef) +// Function: get_named_args(positional, named, _undef) // Usage: -// function f(anon1=_undef, anon2=_undef,..., +// function f(pos1=_undef, pos2=_undef,..., // named1=_undef, named2=_undef, ...) = -// let(args = get_named_args([anon1, anon2, ...], -// [[named1, default1], [named2, default2], ...])) +// let(args = get_named_args([pos1, pos2, ...], +// [[named1, default1], [named2, default2], ...]), +// named1=args[0], named2=args[1], ...) // ... // Description: -// given a set of anonymous and named arguments, returns the values of -// named arguments, in order. -// - All named arguments which were provided by the user take the -// value provided. +// Given the values of some positional and named arguments, +// returns a list of the values assigned to named arguments, +// in the following way: +// - All named arguments which were explicitly assigned in the +// function call take the value provided. // - All named arguments which were not provided by the user are -// affected from anonymous arguments, in order. +// affected from positional arguments; the priority order in which +// these are assigned is given by the `priority` argument, while the +// positional assignation is done in the order of the named arguments. // - Any remaining named arguments take the provided default values. // Arguments: -// anonymous = the list of values of anonymous arguments. -// named = the list of [passed-value, default value] of named arguments. -// _undef = the default value used by the calling function for all -// arguments (this is *not* undef, or any value that the user might -// purposely want to use as an argument value). +// positional = the list of values of positional arguments. +// named = the list of named arguments; each entry of the list has the +// form [passed-value, default-value, priority], where +// passed-value is the value that was passed at function call; +// default-value is the value that will be used if nothing is read +// from either named or positional arguments; +// priority is the priority assigned to this argument. +// _undef = the default value used by the calling function for all arguments (default is some random string that you will never use). (this is *not* undef, or any value that the user might purposely want to use as an argument value). +// +// If only k positional arguments are used, then the k named values +// with lowest 'priority' value (among the unassigned ones) will get them. +// The arguments will be assigned in the order of the named values. +// By default these two orders coincide. +// +// // Examples: // function f(arg1=_undef, arg2=_undef, arg3=_undef, // named1=_undef, named2=_undef, named3=_undef) = @@ -349,16 +363,29 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // result of `dd if=/dev/random bs=32 count=1 |base64` : _undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; -function get_named_args(anonymous, named,_undef=_undef) = - /* u: set of undefined indices in named arguments */ - let(from_anon = [for(p=enumerate(named)) if(p[1][0]==_undef) p[0]], - n = len(anonymous)) - echo("from_anon:", from_anon) +/* Note: however tempting it might be, it is *not* possible to accept + * named argument as a list [named1, named2, ...] (without default + * values), because the values [named1, named2...] themselves might be + * lists, and we will not be able to distinguish the two cases. */ +function get_named_args(positional, named,_undef=_undef) = + let(deft = [for(p=named) p[1]], // default is undef + // indices of the values to fetch from positional args: + unknown = [for(x=enumerate(named)) if(x[1][0]==_undef) x[0]], + // number of values given to positional arguments: + n_positional = count_true([for(p=positional) p!=_undef])) + assert(n_positional <= len(unknown), + str("too many positional arguments (", n_positional, " given, ", + len(unknown), " required)")) + let( + // those elements which have no priority assigned go last (prio=+∞): + prio = sortidx([for(u=unknown) default(named[u][2], 1/0)]), + // list of indices of values assigned from positional arguments: + assigned = sort([for(i=[0:1:n_positional-1]) prio[i]])) [ for(e = enumerate(named)) - // if the value is defined, return it: - e[1][0] != _undef ? e[1][0] : - let(k = anonymous[search(e[0], from_anon)[0]]) - k != _undef ? k : e[1][1] ]; + let(idx=e[0], val=e[1][0], ass=search(idx, assigned)) + val != _undef ? val : + ass != [] ? positional[ass[0]] : + deft[idx] ]; // Function: scalar_vec3() // Usage: // scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index 8c4bc96..46b05e8 100644 --- a/mutators.scad +++ b/mutators.scad @@ -121,16 +121,9 @@ module half_of(v=UP, cp, s=1000, planar=false) function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, v=_undef, cp=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3, _arg4], - [[v], [cp, 0], [p], [s, 1e4]]), + [[v,undef,0], [cp,0,2], [p,undef,1], [s,1e4,3]]), v=args[0], cp0=args[1], p=args[2], s=args[3], cp = is_num(cp0) ? cp0*unit(v) : cp0) - echo("_undef=", _undef) - echo("v=", v) - echo("cp=", cp) - echo("p=", p) - echo("vnf?", is_vnf(p)) - echo("region?", is_region(p)) - echo("s=", s) assert(is_vector(v,2)||is_vector(v,3), "must provide a half-plane or half-space") let(d=len(v)) @@ -164,11 +157,15 @@ function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, : assert(false, "must pass either a point, a path, a region, or a VNF"); -// Module: left_half() +// Function&Module: left_half() // -// Usage: +// Usage: as module // left_half([s], [x]) ... // left_half(planar=true, [s], [x]) ... +// Usage: as function +// left_half([s], [x], path) +// left_half([s], [x], region) +// left_half([s], [x], vnf) // // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it. @@ -197,10 +194,16 @@ module left_half(s=1000, x=0, planar=false) } } } +function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[1,0,0], cp=x, p=p); -// Module: right_half() +// Function&Module: right_half() // // Usage: // right_half([s], [x]) ... @@ -233,10 +236,16 @@ module right_half(s=1000, x=0, planar=false) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[-1,0,0], cp=x, p=p); -// Module: front_half() +// Function&Module: front_half() // // Usage: // front_half([s], [y]) ... @@ -269,10 +278,16 @@ module front_half(s=1000, y=0, planar=false) } } } +function front_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,1,0], cp=x, p=p); -// Module: back_half() +// Function&Module: back_half() // // Usage: // back_half([s], [y]) ... @@ -305,10 +320,16 @@ module back_half(s=1000, y=0, planar=false) } } } +function back_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,-1,0], cp=x, p=p); -// Module: bottom_half() +// Function&Module: bottom_half() // // Usage: // bottom_half([s], [z]) ... @@ -333,10 +354,16 @@ module bottom_half(s=1000, z=0) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,0,-1], cp=x, p=p); -// Module: top_half() +// Function&Module: top_half() // // Usage: // top_half([s], [z]) ... @@ -361,6 +388,12 @@ module top_half(s=1000, z=0) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,0,1], cp=x, p=p); diff --git a/vnf.scad b/vnf.scad index 8b40841..95396f5 100644 --- a/vnf.scad +++ b/vnf.scad @@ -924,7 +924,7 @@ function _vnf_halfspace_paths(edges, i=0, paths=[]) = */ i >= len(edges) ? paths : // termination condition let(e = edges[i], - s = [for(x=enumerate(paths)) if(x[1][len(x[1])-1] == e[0]) x[0]]) + s = [for(x=enumerate(paths)) if(last(x[1]) == e[0]) x[0]]) _vnf_halfspace_paths(edges, i+1, // if cannot attach to previous path: create a new one s == [] ? concat(paths, [e]) : @@ -947,7 +947,7 @@ function vnf_halfspace(_arg1=_undef, _arg2=_undef, newedges=[for(x=tmp2) each x[1]], // generate new faces paths=_vnf_halfspace_paths(newedges), - loops=[for(p=paths) if(p[0] == p[len(p)-1]) p]) + loops=[for(p=paths) if(p[0] == last(p)) p]) [coords, concat(newfaces, loops)]; // From 4af725eec16b8c3d9f86625525c59173b453557b Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:25:57 +0100 Subject: [PATCH 29/42] reset is_region() to pass tests --- regions.scad | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/regions.scad b/regions.scad index 8923751..acd66d9 100644 --- a/regions.scad +++ b/regions.scad @@ -20,8 +20,7 @@ // is_region(x); // Description: // Returns true if the given item looks like a region. A region is defined as a list of zero or more paths. -function is_region(x) = - is_list(x) && all([for(y=x) is_path(y, len(x[0][0]))]); +function is_region(x) = is_list(x) && is_path(x.x); // Function: close_region() From 328a7e8daa3214ea4b34647ed070a338f03f86a4 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:34:59 +0100 Subject: [PATCH 30/42] fixed indentation --- common.scad | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common.scad b/common.scad index a0abd81..abd0a7f 100644 --- a/common.scad +++ b/common.scad @@ -314,10 +314,10 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // Function: get_named_args(positional, named, _undef) // Usage: -// function f(pos1=_undef, pos2=_undef,..., -// named1=_undef, named2=_undef, ...) = -// let(args = get_named_args([pos1, pos2, ...], -// [[named1, default1], [named2, default2], ...]), +// function f(pos1=_undef, pos2=_undef,..., +// named1=_undef, named2=_undef, ...) = +// let(args = get_named_args([pos1, pos2, ...], +// [[named1, default1], [named2, default2], ...]), // named1=args[0], named2=args[1], ...) // ... // Description: From 4dff5e7fea64189a87486c2ea4a465dcbae6ed41 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:36:35 +0100 Subject: [PATCH 31/42] fixed indentation --- common.scad | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/common.scad b/common.scad index abd0a7f..eaf8671 100644 --- a/common.scad +++ b/common.scad @@ -348,16 +348,12 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // // // Examples: -// function f(arg1=_undef, arg2=_undef, arg3=_undef, -// named1=_undef, named2=_undef, named3=_undef) = -// let(named = get_named_args([arg1, arg2, arg3], -// [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) -// named; -// echo(f()); // ["default1", "default2", "default3"] -// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] -// echo(f("given1")); // ["given1", "default2", "default3"] -// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] -// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// function f(arg1=_undef, arg2=_undef, arg3=_undef, named1=_undef, named2=_undef, named3=_undef) = let(named = get_named_args([arg1, arg2, arg3], [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) named; +// echo(f()); // ["default1", "default2", "default3"] +// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] +// echo(f("given1")); // ["given1", "default2", "default3"] +// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] +// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] // a value that the user should never enter randomly; // result of `dd if=/dev/random bs=32 count=1 |base64` : From 96bd60aceb6a51691812f73e176d2db7f6a6e64e Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 01:01:39 +0100 Subject: [PATCH 32/42] half_of() coded as a function for points, paths and regions; VNFs still TODO --- mutators.scad | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/mutators.scad b/mutators.scad index a5a4634..cca657f 100644 --- a/mutators.scad +++ b/mutators.scad @@ -64,13 +64,20 @@ module bounding_box(excess=0) { } -// Module: half_of() +// Function&Module: half_of() // -// Usage: +// Usage: as module // half_of(v, [cp], [s]) ... +// Usage: as function +// half_of(v, [cp], p, [s])... // // Description: // Slices an object at a cut plane, and masks away everything that is on one side. +// * Called as a function with a path in the `p` argument, returns the +// intersection of path `p` and given half-space. +// * Called as a function with a 2D path in the `p` argument +// and a 2D vector `p`, returns the intersection of path `p` and given +// half-plane. // // Arguments: // v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP) @@ -111,6 +118,54 @@ module half_of(v=UP, cp, s=1000, planar=false) } } +function half_of(v, arg1, arg2, cp, p, s=1e4) = + /* may be called as either: + * p= cp= + * 1. (v, p) arg1 0 + * 2. (v, p=p) p 0 + * 3. (v, cp, p) arg2 arg1 + * 4. (v, cp=cp, p) arg1 p + * 5. (v, cp, p=p) p arg1 + * 6. (v, cp=cp, p=p)p cp + */ + /* FIXME: add tests for the various argument naming schemes */ + let(p_=p, cp_=cp, // keep names p and cp clean + p = !is_undef(p_) ? p_ : // cases 2.5.6. + !is_undef(arg2) ? arg2 : arg1, // cases 3., 1.4. + cp0=!is_undef(cp_) ? cp_ : // cases 4.6. + is_undef(arg1) ? 0*v : // case 2. + !is_undef(arg2) ? arg1 : // case 3. + is_undef(p_) ? 0*v : arg1, // cases 1., 5. + cp = is_num(cp0) ? cp0*unit(v) : cp0) + assert(is_vector(v,2)||is_vector(v,3), + "must provide a half-plane or half-space") + let(d=len(v)) + assert(len(cp) == d, str("cp must have dimension ", d)) + is_vector(p) ? + assert(len(p) == d, str("vector must have dimension ", d)) + let(z=(p-cp)*v) (z >= 0 ? p : p - (z*v)/(v*v)) + : + p == [] ? [] : // special case: empty path remains empty + is_path(p) ? + assert(len(p[0]) == d, str("path must have dimension ", d)) + let(z = [for(x=p) (x-cp)*v]) + [ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [], + // we assume a closed path here; + // to make this correct for an open path, + // just replace this by [] when i==len(p)-1: + let(j=(i+1)%len(p)) + // the remaining path may have flattened sections, but this cannot + // create self-intersection or whiskers: + z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] + : + assert(is_region(p), str("must provide point, path or region")) + assert(len(v) == 2, str("3D vector not compatible with region")) + let(u=unit(v), w=[-u[1], u[0]], + R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // bounding region + intersection(R, p); + // FIXME: find something intelligent to do if p is a VNF + // FIXME: scadlib csg.scad, csg_hspace() + // Module: left_half() // From 16204c6724b3e37d48c778b30eaa079721c41df8 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 17:45:20 +0100 Subject: [PATCH 33/42] Allowed half_of() to work on paths, regions and surfaces. --- common.scad | 46 +++++++++++++++++++ mutators.scad | 19 ++++---- vnf.scad | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/common.scad b/common.scad index 0a08f5d..24b181a 100644 --- a/common.scad +++ b/common.scad @@ -312,7 +312,53 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); +// Function: get_named_args(anonymous, named, _undef) +// Usage: +// function f(anon1=_undef, anon2=_undef,..., +// named1=_undef, named2=_undef, ...) = +// let(args = get_named_args([anon1, anon2, ...], +// [[named1, default1], [named2, default2], ...])) +// ... +// Description: +// given a set of anonymous and named arguments, returns the values of +// named arguments, in order. +// - All named arguments which were provided by the user take the +// value provided. +// - All named arguments which were not provided by the user are +// affected from anonymous arguments, in order. +// - Any remaining named arguments take the provided default values. +// Arguments: +// anonymous = the list of values of anonymous arguments. +// named = the list of [passed-value, default value] of named arguments. +// _undef = the default value used by the calling function for all +// arguments (this is *not* undef, or any value that the user might +// purposely want to use as an argument value). +// Examples: +// function f(arg1=_undef, arg2=_undef, arg3=_undef, +// named1=_undef, named2=_undef, named3=_undef) = +// let(named = get_named_args([arg1, arg2, arg3], +// [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) +// named; +// echo(f()); // ["default1", "default2", "default3"] +// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] +// echo(f("given1")); // ["given1", "default2", "default3"] +// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] +// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// a value that the user should never enter randomly; +// result of `dd if=/dev/random bs=32 count=1 |base64` : +_undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; + +function get_named_args(anonymous, named,_undef=_undef) = + /* u: set of undefined indices in named arguments */ + let(from_anon = [for(p=enumerate(named)) if(p[1][0]==_undef) p[0]], + n = len(anonymous)) + echo("from_anon:", from_anon) + [ for(e = enumerate(named)) + // if the value is defined, return it: + e[1][0] != _undef ? e[1][0] : + let(k = anonymous[search(e[0], from_anon)[0]]) + k != _undef ? k : e[1][1] ]; // Function: scalar_vec3() // Usage: // scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index cca657f..49115f4 100644 --- a/mutators.scad +++ b/mutators.scad @@ -118,7 +118,7 @@ module half_of(v=UP, cp, s=1000, planar=false) } } -function half_of(v, arg1, arg2, cp, p, s=1e4) = +function half_of(v, arg1, arg2, s=1e4, cp, p) = /* may be called as either: * p= cp= * 1. (v, p) arg1 0 @@ -158,14 +158,15 @@ function half_of(v, arg1, arg2, cp, p, s=1e4) = // create self-intersection or whiskers: z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] : - assert(is_region(p), str("must provide point, path or region")) - assert(len(v) == 2, str("3D vector not compatible with region")) - let(u=unit(v), w=[-u[1], u[0]], - R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // bounding region - intersection(R, p); - // FIXME: find something intelligent to do if p is a VNF - // FIXME: scadlib csg.scad, csg_hspace() - + is_region(p) ? + assert(len(v) == 2, str("3D vector not compatible with region")) + let(u=unit(v), w=[-u[1], u[0]], + R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // half-plane + intersection(R, p) + : + is_vnf(p) ? + vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : + assert(false, "must pass either a point, a path, a region, or a VNF"); // Module: left_half() // diff --git a/vnf.scad b/vnf.scad index bd5121a..8b40841 100644 --- a/vnf.scad +++ b/vnf.scad @@ -827,5 +827,130 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { color([0.5,0.5,0.5,0.5]) vnf_polyhedron(vnf); } +// Section: VNF transformations +// +// Function: vnf_halfspace(halfspace, vnf) +// Usage: +// vnf_halfspace([a,b,c,d], vnf) +// Description: +// returns the intersection of the VNF with the given half-space. +// Arguments: +// halfspace = half-space to intersect with, given as the four +// coefficients of the affine inequation a*x+b*y+c*z+d ≥ 0. +function _vnf_halfspace_pts(halfspace, points, faces, + inside=undef, coords=[], map=[]) = +/* Recursive function to compute the intersection of points (and edges, + * but not faces) with with the half-space. + * Parameters: + * halfspace a vector(4) + * points a list of points3d + * faces a list of indexes in points + * inside a vector{bool} determining which points belong to the + * half-space; if undef, it is initialized at first loop. + * coords the coordinates of the points in the intersection + * map the logical map (old point) → (new point(s)): + * if point i is kept, then map[i] = new-index-for-i; + * if point i is dropped, then map[i] = [[j1, k1], [j2, k2], …], + * where points j1,… are kept (old index) + * and k1,… are the matching intersections (new index). + * Returns the triple [coords, map, inside]. + * + */ + let(i=len(map), n=len(coords)) // we are currently processing point i + // termination test: + i >= len(points) ? [ coords, map, inside ] : + let(inside = !is_undef(inside) ? inside : + [for(x=points) halfspace*concat(x,[1]) >= 0], + pi = points[i]) + // inside half-space: keep the point (and reindex) + inside[i] ? _vnf_halfspace_pts(halfspace, points, faces, inside, + concat(coords, [pi]), concat(map, [n])) + : // else: compute adjacent vertices (adj) + let(adj = unique([for(f=faces) let(m=len(f), j=search(i, f)[0]) + each if(j!=undef) [f[(j+1)%m], f[(j+m-1)%m]] ]), + // filter those which lie in half-space: + adj2 = [for(x=adj) if(inside[x]) x], + zi = halfspace*concat(pi, [1])) + _vnf_halfspace_pts(halfspace, points, faces, inside, + // new points: we append all these intersection points + concat(coords, [for(j=adj2) let(zj=halfspace*concat(points[j],[1])) + (zi*points[j]-zj*pi)/(zi-zj)]), + // map: we add the info + concat(map, [[for(y=enumerate(adj2)) [y[1], n+y[0]]]])); +function _vnf_halfspace_face(face, map, inside, i=0, + newface=[], newedge=[], exit) = +/* Recursive function to intersect a face of the VNF with the half-plane. + * Arguments: + * face: the list of points of the face (old indices). + * map: as produced by _vnf_halfspace_pts + * inside: vector{bool} containing half-space info + * i: index for iteration + * exit: boolean; is first point in newedge an exit or an entrance from + * half-space? + * newface: list of (new indexes of) points on the face + * newedge: list of new points on the plane (even number of points) + * Return value: [newface, new-edges], where new-edges is a list of + * pairs [entrance-node, exit-node] (new indices). + */ +// termination condition: + (i >= len(face)) ? [ newface, + // if exit==true then we return newedge[1,0], newedge[3,2], ... + // otherwise newedge[0,1], newedge[2,3], ...; + // all edges are oriented (entrance->exit), so that by following the + // arrows we obtain a correctly-oriented face: + let(k = exit ? 0 : 1) + [for(i=[0:2:len(newedge)-2]) [newedge[i+k], newedge[i+1-k]]] ] + : // recursion case: p is current point on face, q is next point + let(p = face[i], q = face[(i+1)%len(face)], + // if p is inside half-plane, keep it in the new face: + newface0 = inside[p] ? concat(newface, [map[p]]) : newface) + // if the current segment does not intersect, this is all: + inside[p] == inside[q] ? _vnf_halfspace_face(face, map, inside, i+1, + newface0, newedge, exit) + : // otherwise, we must add the intersection point: + // rename the two points p,q as inner and outer point: + let(in = inside[p] ? p : q, out = p+q-in, + inter=[for(a=map[out]) if(a[0]==in) a[1]][0]) + _vnf_halfspace_face(face, map, inside, i+1, + concat(newface0, [inter]), + concat(newedge, [inter]), + is_undef(exit) ? inside[p] : exit); +function _vnf_halfspace_paths(edges, i=0, paths=[]) = +/* given a set of oriented edges [x,y], + returns all paths [x,y,z,..] that may be formed from these edges. + A closed path will be returned with equal first and last point. + i: index of currently examined edge + */ + i >= len(edges) ? paths : // termination condition + let(e = edges[i], + s = [for(x=enumerate(paths)) if(x[1][len(x[1])-1] == e[0]) x[0]]) + _vnf_halfspace_paths(edges, i+1, + // if cannot attach to previous path: create a new one + s == [] ? concat(paths, [e]) : + // otherwise, attach to found path + [for(x=enumerate(paths)) x[0]==s[0] ? concat(x[1], [e[1]]) : x[1]]); +function vnf_halfspace(_arg1=_undef, _arg2=_undef, + halfspace=_undef, vnf=_undef) = + // here is where we wish that OpenSCAD had array lvalues... + let(args=get_named_args([_arg1, _arg2], [[halfspace],[vnf]]), + halfspace=args[0], vnf=args[1]) + assert(is_vector(halfspace, 4), + "half-space must be passed as a length 4 affine form") + assert(is_vnf(vnf), "must pass a vnf") + // read points + let(tmp1=_vnf_halfspace_pts(halfspace, vnf[0], vnf[1]), + coords=tmp1[0], map=tmp1[1], inside=tmp1[2], + // cut faces and generate edges + tmp2= [for(f=vnf[1]) _vnf_halfspace_face(f, map, inside)], + newfaces=[for(x=tmp2) if(x[0]!=[]) x[0]], + newedges=[for(x=tmp2) each x[1]], + // generate new faces + paths=_vnf_halfspace_paths(newedges), + loops=[for(p=paths) if(p[0] == p[len(p)-1]) p]) + [coords, concat(newfaces, loops)]; + +// +// // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap +// From be8b1036d81c03b0c55bf15215cb166bc6779dda Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 18:00:31 +0100 Subject: [PATCH 34/42] Improved a bit on is_region(); use get_named_args() in half_of() --- mutators.scad | 36 ++++++++++++++++-------------------- regions.scad | 3 ++- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/mutators.scad b/mutators.scad index 49115f4..8c4bc96 100644 --- a/mutators.scad +++ b/mutators.scad @@ -118,25 +118,19 @@ module half_of(v=UP, cp, s=1000, planar=false) } } -function half_of(v, arg1, arg2, s=1e4, cp, p) = - /* may be called as either: - * p= cp= - * 1. (v, p) arg1 0 - * 2. (v, p=p) p 0 - * 3. (v, cp, p) arg2 arg1 - * 4. (v, cp=cp, p) arg1 p - * 5. (v, cp, p=p) p arg1 - * 6. (v, cp=cp, p=p)p cp - */ - /* FIXME: add tests for the various argument naming schemes */ - let(p_=p, cp_=cp, // keep names p and cp clean - p = !is_undef(p_) ? p_ : // cases 2.5.6. - !is_undef(arg2) ? arg2 : arg1, // cases 3., 1.4. - cp0=!is_undef(cp_) ? cp_ : // cases 4.6. - is_undef(arg1) ? 0*v : // case 2. - !is_undef(arg2) ? arg1 : // case 3. - is_undef(p_) ? 0*v : arg1, // cases 1., 5. +function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, + v=_undef, cp=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3, _arg4], + [[v], [cp, 0], [p], [s, 1e4]]), + v=args[0], cp0=args[1], p=args[2], s=args[3], cp = is_num(cp0) ? cp0*unit(v) : cp0) + echo("_undef=", _undef) + echo("v=", v) + echo("cp=", cp) + echo("p=", p) + echo("vnf?", is_vnf(p)) + echo("region?", is_region(p)) + echo("s=", s) assert(is_vector(v,2)||is_vector(v,3), "must provide a half-plane or half-space") let(d=len(v)) @@ -158,14 +152,16 @@ function half_of(v, arg1, arg2, s=1e4, cp, p) = // create self-intersection or whiskers: z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ] : + is_vnf(p) ? + // we must put is_vnf() before is_region(), because most triangulated + // VNFs will pass is_region() test + vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : is_region(p) ? assert(len(v) == 2, str("3D vector not compatible with region")) let(u=unit(v), w=[-u[1], u[0]], R=[[cp+s*w, cp+s*(v+v), cp+s*(v-w), cp-s*w]]) // half-plane intersection(R, p) : - is_vnf(p) ? - vnf_halfspace(halfspace=concat(v,[-v*cp]), vnf=p) : assert(false, "must pass either a point, a path, a region, or a VNF"); // Module: left_half() diff --git a/regions.scad b/regions.scad index acd66d9..8923751 100644 --- a/regions.scad +++ b/regions.scad @@ -20,7 +20,8 @@ // is_region(x); // Description: // Returns true if the given item looks like a region. A region is defined as a list of zero or more paths. -function is_region(x) = is_list(x) && is_path(x.x); +function is_region(x) = + is_list(x) && all([for(y=x) is_path(y, len(x[0][0]))]); // Function: close_region() From 1d324128e4cbc6f1a6496147cacb340726e70c42 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:21:05 +0100 Subject: [PATCH 35/42] Added more flexibility to get_named_args(), and all the left_half(), etc. as functions --- arrays.scad | 12 +++++++++ common.scad | 73 +++++++++++++++++++++++++++++++++++---------------- mutators.scad | 63 +++++++++++++++++++++++++++++++++----------- vnf.scad | 4 +-- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/arrays.scad b/arrays.scad index e23ef7b..967109b 100644 --- a/arrays.scad +++ b/arrays.scad @@ -89,6 +89,18 @@ function select(list, start, end=undef) = : concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ; +// Function: last() +// Description: +// Returns the last element of a list, or undef if empty. +// Usage: +// last(list) +// Arguments: +// list = The list to get the last element of. +// Example: +// l = [3,4,5,6,7,8,9]; +// last(l); // Returns 9. +function last(list) = list[len(list)-1]; + // Function: slice() // Description: // Returns a slice of a list. The first item is index 0. diff --git a/common.scad b/common.scad index 24b181a..a0abd81 100644 --- a/common.scad +++ b/common.scad @@ -312,27 +312,41 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); -// Function: get_named_args(anonymous, named, _undef) +// Function: get_named_args(positional, named, _undef) // Usage: -// function f(anon1=_undef, anon2=_undef,..., +// function f(pos1=_undef, pos2=_undef,..., // named1=_undef, named2=_undef, ...) = -// let(args = get_named_args([anon1, anon2, ...], -// [[named1, default1], [named2, default2], ...])) +// let(args = get_named_args([pos1, pos2, ...], +// [[named1, default1], [named2, default2], ...]), +// named1=args[0], named2=args[1], ...) // ... // Description: -// given a set of anonymous and named arguments, returns the values of -// named arguments, in order. -// - All named arguments which were provided by the user take the -// value provided. +// Given the values of some positional and named arguments, +// returns a list of the values assigned to named arguments, +// in the following way: +// - All named arguments which were explicitly assigned in the +// function call take the value provided. // - All named arguments which were not provided by the user are -// affected from anonymous arguments, in order. +// affected from positional arguments; the priority order in which +// these are assigned is given by the `priority` argument, while the +// positional assignation is done in the order of the named arguments. // - Any remaining named arguments take the provided default values. // Arguments: -// anonymous = the list of values of anonymous arguments. -// named = the list of [passed-value, default value] of named arguments. -// _undef = the default value used by the calling function for all -// arguments (this is *not* undef, or any value that the user might -// purposely want to use as an argument value). +// positional = the list of values of positional arguments. +// named = the list of named arguments; each entry of the list has the +// form [passed-value, default-value, priority], where +// passed-value is the value that was passed at function call; +// default-value is the value that will be used if nothing is read +// from either named or positional arguments; +// priority is the priority assigned to this argument. +// _undef = the default value used by the calling function for all arguments (default is some random string that you will never use). (this is *not* undef, or any value that the user might purposely want to use as an argument value). +// +// If only k positional arguments are used, then the k named values +// with lowest 'priority' value (among the unassigned ones) will get them. +// The arguments will be assigned in the order of the named values. +// By default these two orders coincide. +// +// // Examples: // function f(arg1=_undef, arg2=_undef, arg3=_undef, // named1=_undef, named2=_undef, named3=_undef) = @@ -349,16 +363,29 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // result of `dd if=/dev/random bs=32 count=1 |base64` : _undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; -function get_named_args(anonymous, named,_undef=_undef) = - /* u: set of undefined indices in named arguments */ - let(from_anon = [for(p=enumerate(named)) if(p[1][0]==_undef) p[0]], - n = len(anonymous)) - echo("from_anon:", from_anon) +/* Note: however tempting it might be, it is *not* possible to accept + * named argument as a list [named1, named2, ...] (without default + * values), because the values [named1, named2...] themselves might be + * lists, and we will not be able to distinguish the two cases. */ +function get_named_args(positional, named,_undef=_undef) = + let(deft = [for(p=named) p[1]], // default is undef + // indices of the values to fetch from positional args: + unknown = [for(x=enumerate(named)) if(x[1][0]==_undef) x[0]], + // number of values given to positional arguments: + n_positional = count_true([for(p=positional) p!=_undef])) + assert(n_positional <= len(unknown), + str("too many positional arguments (", n_positional, " given, ", + len(unknown), " required)")) + let( + // those elements which have no priority assigned go last (prio=+∞): + prio = sortidx([for(u=unknown) default(named[u][2], 1/0)]), + // list of indices of values assigned from positional arguments: + assigned = sort([for(i=[0:1:n_positional-1]) prio[i]])) [ for(e = enumerate(named)) - // if the value is defined, return it: - e[1][0] != _undef ? e[1][0] : - let(k = anonymous[search(e[0], from_anon)[0]]) - k != _undef ? k : e[1][1] ]; + let(idx=e[0], val=e[1][0], ass=search(idx, assigned)) + val != _undef ? val : + ass != [] ? positional[ass[0]] : + deft[idx] ]; // Function: scalar_vec3() // Usage: // scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index 8c4bc96..46b05e8 100644 --- a/mutators.scad +++ b/mutators.scad @@ -121,16 +121,9 @@ module half_of(v=UP, cp, s=1000, planar=false) function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, v=_undef, cp=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3, _arg4], - [[v], [cp, 0], [p], [s, 1e4]]), + [[v,undef,0], [cp,0,2], [p,undef,1], [s,1e4,3]]), v=args[0], cp0=args[1], p=args[2], s=args[3], cp = is_num(cp0) ? cp0*unit(v) : cp0) - echo("_undef=", _undef) - echo("v=", v) - echo("cp=", cp) - echo("p=", p) - echo("vnf?", is_vnf(p)) - echo("region?", is_region(p)) - echo("s=", s) assert(is_vector(v,2)||is_vector(v,3), "must provide a half-plane or half-space") let(d=len(v)) @@ -164,11 +157,15 @@ function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, : assert(false, "must pass either a point, a path, a region, or a VNF"); -// Module: left_half() +// Function&Module: left_half() // -// Usage: +// Usage: as module // left_half([s], [x]) ... // left_half(planar=true, [s], [x]) ... +// Usage: as function +// left_half([s], [x], path) +// left_half([s], [x], region) +// left_half([s], [x], vnf) // // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it. @@ -197,10 +194,16 @@ module left_half(s=1000, x=0, planar=false) } } } +function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[1,0,0], cp=x, p=p); -// Module: right_half() +// Function&Module: right_half() // // Usage: // right_half([s], [x]) ... @@ -233,10 +236,16 @@ module right_half(s=1000, x=0, planar=false) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[-1,0,0], cp=x, p=p); -// Module: front_half() +// Function&Module: front_half() // // Usage: // front_half([s], [y]) ... @@ -269,10 +278,16 @@ module front_half(s=1000, y=0, planar=false) } } } +function front_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,1,0], cp=x, p=p); -// Module: back_half() +// Function&Module: back_half() // // Usage: // back_half([s], [y]) ... @@ -305,10 +320,16 @@ module back_half(s=1000, y=0, planar=false) } } } +function back_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,-1,0], cp=x, p=p); -// Module: bottom_half() +// Function&Module: bottom_half() // // Usage: // bottom_half([s], [z]) ... @@ -333,10 +354,16 @@ module bottom_half(s=1000, z=0) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,0,-1], cp=x, p=p); -// Module: top_half() +// Function&Module: top_half() // // Usage: // top_half([s], [z]) ... @@ -361,6 +388,12 @@ module top_half(s=1000, z=0) } } } +function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, + x=_undef, p=_undef, s=_undef) = + let(args=get_named_args([_arg1, _arg2, _arg3], + [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + x=args[0], p=args[1], s=args[2]) + half_of(v=[0,0,1], cp=x, p=p); diff --git a/vnf.scad b/vnf.scad index 8b40841..95396f5 100644 --- a/vnf.scad +++ b/vnf.scad @@ -924,7 +924,7 @@ function _vnf_halfspace_paths(edges, i=0, paths=[]) = */ i >= len(edges) ? paths : // termination condition let(e = edges[i], - s = [for(x=enumerate(paths)) if(x[1][len(x[1])-1] == e[0]) x[0]]) + s = [for(x=enumerate(paths)) if(last(x[1]) == e[0]) x[0]]) _vnf_halfspace_paths(edges, i+1, // if cannot attach to previous path: create a new one s == [] ? concat(paths, [e]) : @@ -947,7 +947,7 @@ function vnf_halfspace(_arg1=_undef, _arg2=_undef, newedges=[for(x=tmp2) each x[1]], // generate new faces paths=_vnf_halfspace_paths(newedges), - loops=[for(p=paths) if(p[0] == p[len(p)-1]) p]) + loops=[for(p=paths) if(p[0] == last(p)) p]) [coords, concat(newfaces, loops)]; // From 8a7555cc768870c75172123b19f5f17d9084b9ce Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:25:57 +0100 Subject: [PATCH 36/42] reset is_region() to pass tests --- regions.scad | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/regions.scad b/regions.scad index 8923751..acd66d9 100644 --- a/regions.scad +++ b/regions.scad @@ -20,8 +20,7 @@ // is_region(x); // Description: // Returns true if the given item looks like a region. A region is defined as a list of zero or more paths. -function is_region(x) = - is_list(x) && all([for(y=x) is_path(y, len(x[0][0]))]); +function is_region(x) = is_list(x) && is_path(x.x); // Function: close_region() From 8ab4b7c538de4c2a60590242d1b71d88fee8656d Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:34:59 +0100 Subject: [PATCH 37/42] fixed indentation --- common.scad | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common.scad b/common.scad index a0abd81..abd0a7f 100644 --- a/common.scad +++ b/common.scad @@ -314,10 +314,10 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // Function: get_named_args(positional, named, _undef) // Usage: -// function f(pos1=_undef, pos2=_undef,..., -// named1=_undef, named2=_undef, ...) = -// let(args = get_named_args([pos1, pos2, ...], -// [[named1, default1], [named2, default2], ...]), +// function f(pos1=_undef, pos2=_undef,..., +// named1=_undef, named2=_undef, ...) = +// let(args = get_named_args([pos1, pos2, ...], +// [[named1, default1], [named2, default2], ...]), // named1=args[0], named2=args[1], ...) // ... // Description: From e983f6599dfea2901c12f06081293e9a17f1c849 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:36:35 +0100 Subject: [PATCH 38/42] fixed indentation --- common.scad | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/common.scad b/common.scad index abd0a7f..eaf8671 100644 --- a/common.scad +++ b/common.scad @@ -348,16 +348,12 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // // // Examples: -// function f(arg1=_undef, arg2=_undef, arg3=_undef, -// named1=_undef, named2=_undef, named3=_undef) = -// let(named = get_named_args([arg1, arg2, arg3], -// [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) -// named; -// echo(f()); // ["default1", "default2", "default3"] -// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] -// echo(f("given1")); // ["given1", "default2", "default3"] -// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] -// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// function f(arg1=_undef, arg2=_undef, arg3=_undef, named1=_undef, named2=_undef, named3=_undef) = let(named = get_named_args([arg1, arg2, arg3], [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) named; +// echo(f()); // ["default1", "default2", "default3"] +// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] +// echo(f("given1")); // ["given1", "default2", "default3"] +// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] +// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] // a value that the user should never enter randomly; // result of `dd if=/dev/random bs=32 count=1 |base64` : From abc8881b00e5c90f8fde73457c84caf44e443d48 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 21:48:43 +0100 Subject: [PATCH 39/42] Fixed indentation issues in docs --- common.scad | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/common.scad b/common.scad index eaf8671..653e900 100644 --- a/common.scad +++ b/common.scad @@ -314,12 +314,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // Function: get_named_args(positional, named, _undef) // Usage: -// function f(pos1=_undef, pos2=_undef,..., -// named1=_undef, named2=_undef, ...) = -// let(args = get_named_args([pos1, pos2, ...], -// [[named1, default1], [named2, default2], ...]), -// named1=args[0], named2=args[1], ...) -// ... +// function f(pos1=_undef, pos2=_undef,...,named1=_undef, named2=_undef, ...) = let(args = get_named_args([pos1, pos2, ...], [[named1, default1], [named2, default2], ...]), named1=args[0], named2=args[1], ...) // Description: // Given the values of some positional and named arguments, // returns a list of the values assigned to named arguments, @@ -331,20 +326,15 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = // these are assigned is given by the `priority` argument, while the // positional assignation is done in the order of the named arguments. // - Any remaining named arguments take the provided default values. -// Arguments: -// positional = the list of values of positional arguments. -// named = the list of named arguments; each entry of the list has the -// form [passed-value, default-value, priority], where -// passed-value is the value that was passed at function call; -// default-value is the value that will be used if nothing is read -// from either named or positional arguments; -// priority is the priority assigned to this argument. -// _undef = the default value used by the calling function for all arguments (default is some random string that you will never use). (this is *not* undef, or any value that the user might purposely want to use as an argument value). -// // If only k positional arguments are used, then the k named values // with lowest 'priority' value (among the unassigned ones) will get them. // The arguments will be assigned in the order of the named values. // By default these two orders coincide. +// Arguments: +// positional = the list of values of positional arguments. +// named = the list of named arguments; each entry of the list has the form [passed-value, default-value, priority], where passed-value is the value that was passed at function call; default-value is the value that will be used if nothing is read from either named or positional arguments; priority is the priority assigned to this argument. +// _undef = the default value used by the calling function for all arguments (default is some random string that you will never use). (this is *not* undef, or any value that the user might purposely want to use as an argument value). +// // // // Examples: From 8b22f3da08e09a8106473fb9e7fd9f8d7c976bb0 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 4 Dec 2020 00:10:23 +0100 Subject: [PATCH 40/42] Wrote better documentation for get_named_args() --- common.scad | 82 +++++++++++++++++++++++++++++++++++---------------- mutators.scad | 35 +++++++++++----------- vnf.scad | 13 ++++---- 3 files changed, 79 insertions(+), 51 deletions(-) diff --git a/common.scad b/common.scad index 653e900..4ca337e 100644 --- a/common.scad +++ b/common.scad @@ -312,38 +312,67 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) = assert(num_defined([h,l,height])<=1,"You must specify only one of `l`, `h`, and `height`") first_defined([h,l,height,dflt]); -// Function: get_named_args(positional, named, _undef) +// Function: get_named_args() // Usage: // function f(pos1=_undef, pos2=_undef,...,named1=_undef, named2=_undef, ...) = let(args = get_named_args([pos1, pos2, ...], [[named1, default1], [named2, default2], ...]), named1=args[0], named2=args[1], ...) // Description: // Given the values of some positional and named arguments, -// returns a list of the values assigned to named arguments, -// in the following way: -// - All named arguments which were explicitly assigned in the -// function call take the value provided. -// - All named arguments which were not provided by the user are -// affected from positional arguments; the priority order in which -// these are assigned is given by the `priority` argument, while the -// positional assignation is done in the order of the named arguments. -// - Any remaining named arguments take the provided default values. -// If only k positional arguments are used, then the k named values -// with lowest 'priority' value (among the unassigned ones) will get them. -// The arguments will be assigned in the order of the named values. -// By default these two orders coincide. +// returns a list of the values assigned to named parameters. +// in the following steps: +// - First, all named parameters which were explicitly assigned in the +// function call take their provided value. +// - Then, any positional arguments are assigned to remaining unassigned +// parameters; this is governed both by the `priority` entries +// (if there are `N` positional arguments, then the `N` parameters with +// lowest `priority` value will be assigned) and by the order of the +// positional arguments (matching that of the assigned named parameters). +// If no priority is given, then these two ordering coincide: +// parameters are assigned in order, starting from the first one. +// - Finally, any remaining named parameters can take default values. +// If no default values are given, then `undef` is used. +// . +// This allows an author to declare a function prototype with named or +// optional parameters, so that the user may then call this function +// using either positional or named parameters. In practice the author +// will declare the function as using *both* positional and named +// parameters, and let `get_named_args()` do the parsing from the whole +// set of arguments. +// See the example below. +// . +// This supports the user explicitly passing `undef` as a function argument. +// To distinguish between an intentional `undef` and +// the absence of an argument, we use a custom `_undef` value +// as a guard marking the absence of any arguments +// (in practice, `_undef` is a random-generated string, +// which will never coincide with any useful user value). +// This forces the author to declare all the function parameters +// as having `_undef` as their default value. // Arguments: // positional = the list of values of positional arguments. -// named = the list of named arguments; each entry of the list has the form [passed-value, default-value, priority], where passed-value is the value that was passed at function call; default-value is the value that will be used if nothing is read from either named or positional arguments; priority is the priority assigned to this argument. -// _undef = the default value used by the calling function for all arguments (default is some random string that you will never use). (this is *not* undef, or any value that the user might purposely want to use as an argument value). +// named = the list of named arguments; each entry of the list has the form `[passed-value, , ]`, where `passed-value` is the value that was passed at function call; `default-value` is the value that will be used if nothing is read from either named or positional arguments; `priority` is the priority assigned to this argument (lower means more priority, default value is `+inf`). Since stable sorting is used, if no priority at all is given, all arguments will be read in order. +// _undef = the default value used by the calling function for all arguments. The default value, `_undef`, is a random string. This value **must** be the default value of all parameters in the outer function call (see example below). // -// -// -// Examples: -// function f(arg1=_undef, arg2=_undef, arg3=_undef, named1=_undef, named2=_undef, named3=_undef) = let(named = get_named_args([arg1, arg2, arg3], [[named1, "default1"], [named2, "default2"], [named3, "default3"]])) named; -// echo(f()); // ["default1", "default2", "default3"] -// echo(f("given2", "given3", named1="given1")); // ["given1", "given2", "given3"] -// echo(f("given1")); // ["given1", "default2", "default3"] -// echo(f(named1="given1", "given2")); // ["given1", "given2", "default3"] -// echo(f(undef, named1="given1", undef)); // ["given1", undef, undef] +// Example: a function with prototype `f(named1,< , named3 >)` +// function f(_p1=_undef, _p2=_undef, _p3=_undef, +// arg1=_undef, arg2=_undef, arg3=_undef) = +// let(named = get_named_args([_p1, _p2, _p3], +// [[arg1, "default1",0], [arg2, "default2",2], [arg3, "default3",1]])) +// named; +// // all default values or all parameters provided: +// echo(f()); +// // ["default1", "default2", "default3"] +// echo(f("given2", "given3", arg1="given1")); +// // ["given1", "given2", "given3"] +// +// // arg1 has highest priority, and arg3 is higher than arg2: +// echo(f("given1")); +// // ["given1", "default2", "default3"] +// echo(f("given3", arg1="given1")); +// // ["given1", "default2", "given3"] +// +// // explicitly passing undef is allowed: +// echo(f(undef, arg1="given1", undef)); +// // ["given1", undef, undef] // a value that the user should never enter randomly; // result of `dd if=/dev/random bs=32 count=1 |base64` : @@ -366,7 +395,8 @@ function get_named_args(positional, named,_undef=_undef) = // those elements which have no priority assigned go last (prio=+∞): prio = sortidx([for(u=unknown) default(named[u][2], 1/0)]), // list of indices of values assigned from positional arguments: - assigned = sort([for(i=[0:1:n_positional-1]) prio[i]])) + assigned = [for(a=sort([for(i=[0:1:n_positional-1]) prio[i]])) + unknown[a]]) [ for(e = enumerate(named)) let(idx=e[0], val=e[1][0], ass=search(idx, assigned)) val != _undef ? val : diff --git a/mutators.scad b/mutators.scad index 46b05e8..f704bdc 100644 --- a/mutators.scad +++ b/mutators.scad @@ -67,17 +67,17 @@ module bounding_box(excess=0) { // Function&Module: half_of() // // Usage: as module -// half_of(v, [cp], [s]) ... +// half_of(v, , ) ... // Usage: as function -// half_of(v, [cp], p, [s])... +// half_of(v, , p, )... // // Description: // Slices an object at a cut plane, and masks away everything that is on one side. // * Called as a function with a path in the `p` argument, returns the -// intersection of path `p` and given half-space. +// intersection of path `p` and given half-space. // * Called as a function with a 2D path in the `p` argument -// and a 2D vector `p`, returns the intersection of path `p` and given -// half-plane. +// and a 2D vector `p`, returns the intersection of path `p` and given +// half-plane. // // Arguments: // v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP) @@ -121,7 +121,7 @@ module half_of(v=UP, cp, s=1000, planar=false) function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, v=_undef, cp=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3, _arg4], - [[v,undef,0], [cp,0,2], [p,undef,1], [s,1e4,3]]), + [[v,undef,0], [cp,0,2], [p,undef,1], [s, 1e4]]), v=args[0], cp0=args[1], p=args[2], s=args[3], cp = is_num(cp0) ? cp0*unit(v) : cp0) assert(is_vector(v,2)||is_vector(v,3), @@ -160,12 +160,12 @@ function half_of(_arg1=_undef, _arg2=_undef, _arg3=_undef, _arg4=_undef, // Function&Module: left_half() // // Usage: as module -// left_half([s], [x]) ... -// left_half(planar=true, [s], [x]) ... +// left_half(, ) ... +// left_half(planar=true, , ) ... // Usage: as function -// left_half([s], [x], path) -// left_half([s], [x], region) -// left_half([s], [x], vnf) +// left_half(, , path) +// left_half(, , region) +// left_half(, , vnf) // // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it. @@ -197,7 +197,7 @@ module left_half(s=1000, x=0, planar=false) function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[1,0,0], cp=x, p=p); @@ -209,6 +209,7 @@ function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, // right_half([s], [x]) ... // right_half(planar=true, [s], [x]) ... // +// // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it. // @@ -239,7 +240,7 @@ module right_half(s=1000, x=0, planar=false) function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[-1,0,0], cp=x, p=p); @@ -281,7 +282,7 @@ module front_half(s=1000, y=0, planar=false) function front_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[0,1,0], cp=x, p=p); @@ -323,7 +324,7 @@ module back_half(s=1000, y=0, planar=false) function back_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[0,-1,0], cp=x, p=p); @@ -357,7 +358,7 @@ module bottom_half(s=1000, z=0) function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[0,0,-1], cp=x, p=p); @@ -391,7 +392,7 @@ module top_half(s=1000, z=0) function right_half(_arg1=_undef, _arg2=_undef, _arg3=_undef, x=_undef, p=_undef, s=_undef) = let(args=get_named_args([_arg1, _arg2, _arg3], - [[x, 0,1], [p,undef,0], [s, 1e4,2]]), + [[x, 0,1], [p,undef,0], [s, 1e4]]), x=args[0], p=args[1], s=args[2]) half_of(v=[0,0,1], cp=x, p=p); diff --git a/vnf.scad b/vnf.scad index 95396f5..a75a36c 100644 --- a/vnf.scad +++ b/vnf.scad @@ -829,14 +829,14 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { // Section: VNF transformations // + // Function: vnf_halfspace(halfspace, vnf) // Usage: // vnf_halfspace([a,b,c,d], vnf) // Description: // returns the intersection of the VNF with the given half-space. // Arguments: -// halfspace = half-space to intersect with, given as the four -// coefficients of the affine inequation a*x+b*y+c*z+d ≥ 0. +// halfspace = half-space to intersect with, given as the four coefficients of the affine inequation a\*x+b\*y+c\*z≥ d. function _vnf_halfspace_pts(halfspace, points, faces, inside=undef, coords=[], map=[]) = @@ -861,7 +861,7 @@ function _vnf_halfspace_pts(halfspace, points, faces, // termination test: i >= len(points) ? [ coords, map, inside ] : let(inside = !is_undef(inside) ? inside : - [for(x=points) halfspace*concat(x,[1]) >= 0], + [for(x=points) halfspace*concat(x,[-1]) >= 0], pi = points[i]) // inside half-space: keep the point (and reindex) inside[i] ? _vnf_halfspace_pts(halfspace, points, faces, inside, @@ -871,10 +871,10 @@ function _vnf_halfspace_pts(halfspace, points, faces, each if(j!=undef) [f[(j+1)%m], f[(j+m-1)%m]] ]), // filter those which lie in half-space: adj2 = [for(x=adj) if(inside[x]) x], - zi = halfspace*concat(pi, [1])) + zi = halfspace*concat(pi, [-1])) _vnf_halfspace_pts(halfspace, points, faces, inside, // new points: we append all these intersection points - concat(coords, [for(j=adj2) let(zj=halfspace*concat(points[j],[1])) + concat(coords, [for(j=adj2) let(zj=halfspace*concat(points[j],[-1])) (zi*points[j]-zj*pi)/(zi-zj)]), // map: we add the info concat(map, [[for(y=enumerate(adj2)) [y[1], n+y[0]]]])); @@ -950,7 +950,4 @@ function vnf_halfspace(_arg1=_undef, _arg2=_undef, loops=[for(p=paths) if(p[0] == last(p)) p]) [coords, concat(newfaces, loops)]; -// -// // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap -// From 245a545b6e11f45d29fb9db866ce2558431dd779 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 4 Dec 2020 09:06:34 +0100 Subject: [PATCH 41/42] vnf_halfspace: fixed loop detection --- vnf.scad | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/vnf.scad b/vnf.scad index a75a36c..a55e204 100644 --- a/vnf.scad +++ b/vnf.scad @@ -916,20 +916,37 @@ function _vnf_halfspace_face(face, map, inside, i=0, concat(newface0, [inter]), concat(newedge, [inter]), is_undef(exit) ? inside[p] : exit); +function _vnf_halfspace_path_search_edge(edge, paths, i=0, ret=[undef,undef]) = +/* given an oriented edge [x,y] and a set of oriented paths, + * returns the indices [i,j] of paths [before, after] given edge + */ + // termination condition + i >= len(paths) ? ret: + _vnf_halfspace_path_search_edge(edge, paths, i+1, + [last(paths[i]) == edge[0] ? i : ret[0], + paths[i][0] == edge[1] ? i : ret[1]]); function _vnf_halfspace_paths(edges, i=0, paths=[]) = /* given a set of oriented edges [x,y], returns all paths [x,y,z,..] that may be formed from these edges. A closed path will be returned with equal first and last point. i: index of currently examined edge */ - i >= len(edges) ? paths : // termination condition - let(e = edges[i], - s = [for(x=enumerate(paths)) if(last(x[1]) == e[0]) x[0]]) - _vnf_halfspace_paths(edges, i+1, - // if cannot attach to previous path: create a new one - s == [] ? concat(paths, [e]) : - // otherwise, attach to found path - [for(x=enumerate(paths)) x[0]==s[0] ? concat(x[1], [e[1]]) : x[1]]); + i >= len(edges) ? paths : // termination condition + let(e=edges[i], s = _vnf_halfspace_path_search_edge(e, paths)) + _vnf_halfspace_paths(edges, i+1, + // we keep all paths untouched by e[i] + concat([for(i=[0:1:len(paths)-1]) if(i!= s[0] && i != s[1]) paths[i]], + is_undef(s[0])? ( + // fresh e: create a new path + is_undef(s[1]) ? [e] : + // e attaches to beginning of previous path + [concat([e[0]], paths[s[1]])] + ) :// edge attaches to end of previous path + is_undef(s[1]) ? [concat(paths[s[0]], [e[1]])] : + // edge merges two paths + s[0] != s[1] ? [concat(paths[s[0]], paths[s[1]])] : + // edge closes a loop + [concat(paths[s[0]], [e[1]])])); function vnf_halfspace(_arg1=_undef, _arg2=_undef, halfspace=_undef, vnf=_undef) = // here is where we wish that OpenSCAD had array lvalues... From 008d1c5002bea1fc9ac04b01332028fb8fc5fa4e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 8 Dec 2020 21:41:50 -0500 Subject: [PATCH 42/42] Add path_sweep2d and change path_sweep to accept ccw shape without producing inverted output. Minor doc fixes. --- skin.scad | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/skin.scad b/skin.scad index 322bc1b..14c348c 100644 --- a/skin.scad +++ b/skin.scad @@ -1298,8 +1298,118 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi all([for(i=idx(start)) approx(start[i],end[i])]), dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****") ) - transforms ? transform_list : sweep(shape, transform_list, closed=false, caps=fullcaps); + transforms ? transform_list : sweep(clockwise_polygon(shape), transform_list, closed=false, caps=fullcaps); +// Function&Module: path_sweep2d() +// Usage: +// path_sweep2d(shape, path, , ) +// Description: +// Takes an input 2D polygon (the shape) and a 2d path and constructs a polyhedron by sweeping the shape along the path. +// When run as a module returns the polyhedron geometry. When run as a function returns a VNF. +// . +// Unlike path_sweep(), local self-intersections (creases in the output) are allowed and do not produce CGAL errors. +// This is accomplished by using offset() calculations, which are more expensive than simply copying the shape along +// the path, so if you do not have local self-intersections, use path_sweep() instead. Note that global self-intersections +// will still give rise to CGAL errors. You should be able to handle these by partitioning your model. The y axis of the +// shape is mapped to the z axis in the swept polyhedron. +// The quality parameter is passed to offset to determine the offset quality. +// Arguments: +// shape = a 2D polygon describing the shape to be swept +// path = a 2D path giving the path to sweep over +// closed = path is a closed loop. Default: false +// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false. +// quality = quality of offset used in calculation. Default: 1 +// convexity = convexity parameter for polyhedron (module only) Default: 10 +// anchor = Translate so anchor point is at the origin. (module only) Default: "origin" +// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0 +// orient = Vector to rotate top towards after spin (module only) +// extent = use extent method for computing anchors. (module only) Default: false +// cp = set centerpoint for anchor computation. (module only) Default: object centroid +// Example: Sine wave example with self-intersections at each peak. This would fail with path_sweep(). +// sinewave = [for(i=[-30:10:360*2+30]) [i/40,3*sin(i)]]; +// path_sweep2d(circle(r=3,$fn=15), sinewave); +// Example: The ends can look weird if they are in a place where self intersection occurs. This is a natural result of how offset behaves at ends of a path. +// coswave = [for(i=[0:10:360*1.5]) [i/40,3*cos(i)]]; +// zrot(-20) +// path_sweep2d( circle(r=3,$fn=15), coswave); +// Example: This closed path example works ok as long as the hole in the center remains open. +// ellipse = yscale(3,p=circle(r=3,$fn=120)); +// path_sweep2d(circle(r=2.5,$fn=32), reverse(ellipse), closed=true); +// Example: When the hole is closed a global intersection renders the model invalid. You can fix this by taking the union of the two (valid) halves. +// ellipse = yscale(3,p=circle(r=3,$fn=120)); +// L = len(ellipse); +// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.2),ceil(L*.8)),closed=false); +// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.7),ceil(L*.3)),closed=false); + +function path_sweep2d(shape, path, closed=false, caps, quality=1) = + let( + caps = is_def(caps) ? caps + : closed ? false : true, + capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])), + fullcaps = is_bool(caps) ? [caps,caps] : caps + ) + assert(capsOK, "caps must be boolean or a list of two booleans") + assert(!closed || !caps, "Cannot make closed shape with caps") + let( + profile = ccw_polygon(shape), + flip = closed && polygon_is_clockwise(path) ? -1 : 1, + path = flip ? reverse(path) : path, + proflist= transpose( + [for(pt = profile) + let( + ofs = offset(path, delta=-flip*pt.x, return_faces=true,closed=closed, quality=quality), + map = subindex(_ofs_vmap(ofs,closed=closed),1) + ) + select(path3d(ofs[0],pt.y),map) + ] + ) + ) + _skin_core([ + each proflist, + if (closed) proflist[0] + ],caps=fullcaps); + +module path_sweep2d(profile, path, closed=false, caps, quality=1, convexity=10, + anchor="origin", cp, spin=0, orient=UP, extent=false) +{ + vnf = path_sweep2d(profile, path, closed, caps, quality); + attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf)) + { + vnf_polyhedron(vnf,convexity=convexity); + children(); + } +} + +// Extract vertex mapping from offset face list. The output of this function +// is a list of pairs [i,j] where i is an index into the parent curve and j is +// an index into the offset curve. It would probably make sense to rewrite +// offset() to return this instead of the face list and have offset_sweep +// use this input to assemble the faces it needs. + +function _ofs_vmap(ofs,closed=false) = + let( // Caclulate length of the first (parent) curve + firstlen = max(flatten(ofs[1]))+1-len(ofs[0]) + ) + [ + for(entry=ofs[1]) _ofs_face_edge(entry,firstlen), + if (!closed) _ofs_face_edge(select(ofs[1],-1),firstlen,second=true) + ]; + + +// Extract first (default) or second edge that connects the parent curve to its offset. The first input +// face is a list of 3 or 4 vertices as indices into the two curves where the parent curve vertices are +// numbered from 0 to firstlen-1 and the offset from firstlen and up. The firstlen pararameter is used +// to determine which curve the vertices belong to and to remove the offset so that the return gives +// the index into each curve with a 0 base. +function _ofs_face_edge(face,firstlen,second=false) = + let( + itry = min_index(face), + i = select(face,itry-1)