From d47fbef5a700a3cda061e6d8e7354b563fcbb7dc Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 01:01:39 +0100 Subject: [PATCH 01/19] 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 91bca08f5041552784868f15cc4258da875d7770 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Thu, 3 Dec 2020 17:45:20 +0100 Subject: [PATCH 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 12/19] 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 13/19] 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 14/19] 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 15/19] 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 16/19] 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 17/19] 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 18/19] 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 19/19] 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...