From 6aaf364f95ed0fc3b4352026daaf821a895b75fd Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 17 Apr 2021 10:52:07 +0000 Subject: [PATCH 1/4] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index c15fc2e..6f7dbfa 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,613]; +BOSL_VERSION = [2,0,614]; // Section: BOSL Library Version Functions From fd59a39863a44c559e2b0c1c3b4a4438a022e0fc Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 19 Apr 2021 03:21:08 +0000 Subject: [PATCH 2/4] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index 6f7dbfa..958d0a6 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,614]; +BOSL_VERSION = [2,0,615]; // Section: BOSL Library Version Functions From 94abf6585781825ab36c7a71aef5a5cee0ef1f47 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 19 Apr 2021 16:58:17 -0400 Subject: [PATCH 3/4] rewrite vnf_halfspace and fix bugs in half_of functions. remove get_named_args --- common.scad | 86 +----------------- mutators.scad | 170 +++++++++++++++++------------------ vnf.scad | 239 ++++++++++++++++++++++---------------------------- 3 files changed, 187 insertions(+), 308 deletions(-) diff --git a/common.scad b/common.scad index 4ec8762..b6926ba 100644 --- a/common.scad +++ b/common.scad @@ -407,7 +407,7 @@ function all_defined(v,recursive=false) = // Usage: // anchr = get_anchor(anchor,center,,); // Topics: Argument Handling -// See Also: get_radius(), get_named_args() +// See Also: get_radius() // Description: // Calculated the correct anchor from `anchor` and `center`. In order: // - If `center` is not `undef` and `center` evaluates as true, then `CENTER` (`[0,0,0]`) is returned. @@ -437,7 +437,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) = // Usage: // r = get_radius(, , , , , , ); // Topics: Argument Handling -// See Also: get_anchor(), get_named_args() +// See Also: get_anchor() // Description: // Given various radii and diameters, returns the most specific radius. If a diameter is most // specific, returns half its value, giving the radius. If no radii or diameters are defined, @@ -480,88 +480,6 @@ function get_radius(r1, r2, r, d1, d2, d, dflt) = : dflt; -// 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], ...) -// Topics: Argument Handling -// See Also: get_anchor(), get_radius() -// Description: -// Given the values of some positional and named arguments, 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, , ]`, 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). -// -// 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] - -/* 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 = [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 : - ass != [] ? positional[ass[0]] : - deft[idx] ]; - - // Function: scalar_vec3() // Usage: // vec = scalar_vec3(v, ); diff --git a/mutators.scad b/mutators.scad index 133dab5..1f954a8 100644 --- a/mutators.scad +++ b/mutators.scad @@ -96,23 +96,23 @@ module bounding_box(excess=0, planar=false) { // Function&Module: half_of() // // Usage: as module -// half_of(v, , ) ... +// half_of(v, , , ) ... // Usage: as function -// half_of(v, , p, )... +// result = half_of(p,v,); // // 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. +// Slices an object at a cut plane, and masks away everything that is on one side. The v parameter is either a plane specification or +// a normal vector. The s parameter is needed for the module +// version to control the size of the masking cube, which affects preview display. +// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation +// is performed in and UP and DOWN are treated as equivalent to BACK and FWD respectively. // // Arguments: +// p = path, region or VNF to slice. (Function version) // v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP) -// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. This can be used to shift where it slices the object at. Default: [0,0,0] -// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100 -// planar = If true, this becomes a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively. +// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0] +// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Ignored for function version. Default: 1000 +// planar = If true, perform a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively. // // Examples: // half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false); @@ -147,23 +147,43 @@ 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]]), - 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), - "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 +function half_of(p, v=UP, cp) = + is_vnf(p) ? + assert(is_vector(v) && (len(v)==3 || len(v)==4),str("Must give 3-vector or plane specification",v)) + assert(select(v,0,2)!=[0,0,0], "vector v must be nonzero") + let( + plane = is_vector(v,4) ? assert(cp==undef, "Don't use cp with plane definition.") v + : is_undef(cp) ? [each v, 0] + : is_num(cp) ? [each v, cp*(v*v)/norm(v)] + : assert(is_vector(cp,3),"Centerpoint must be a 3-vector") + [each v, cp*v] + ) + vnf_halfspace(plane, p) + : is_path(p) || is_region(p) ? + let( + v = (v==UP)? BACK : (v==DOWN)? FWD : v, + cp = is_undef(cp) ? [0,0] + : is_num(cp) ? v*cp + : assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector") + cp + ) + assert(is_vector(v,2) || (is_vector(v,3) && v.z==0),"Must give 2-vector") + assert(!all_zero(v), "Vector v must be nonzero") + let( + bounds = pointlist_bounds(move(-cp,p)), + L = 2*max(flatten(bounds)), + n = unit(v), + u = [-n.y,n.x], + box = [cp+u*L, cp+(v+u)*L, cp+(v-u)*L, cp-u*L] + ) + intersection(box,p) + : assert(false, "Input must be a region, path or VNF"); + + + +/* This code cut 3d paths but leaves behind connecting line segments is_path(p) ? - assert(len(p[0]) == d, str("path must have dimension ", d)) + //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; @@ -174,17 +194,8 @@ function half_of(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF, _arg4=_UNDEF, // 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) - : - assert(false, "must pass either a point, a path, a region, or a VNF"); +*/ + // Function&Module: left_half() // @@ -192,17 +203,16 @@ function half_of(_arg1=_UNDEF, _arg2=_UNDEF, _arg3=_UNDEF, _arg4=_UNDEF, // left_half(, ) ... // left_half(planar=true, , ) ... // Usage: as function -// left_half(, , path) -// left_half(, , region) -// left_half(, , vnf) +// result = left_half(p, ); // // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // x = The X coordinate of the cut-plane. Default: 0 -// planar = If true, this becomes a 2D operation. +// planar = If true, perform a 2D operation. // // Examples: // left_half() sphere(r=20); @@ -223,28 +233,26 @@ 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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[-1,0,0], cp=x, p=p); +function left_half(p,x=0) = half_of(p, LEFT, [x,0,0]); // Function&Module: right_half() // -// Usage: +// Usage: as module // right_half([s], [x]) ... // right_half(planar=true, [s], [x]) ... +// Usage: as function +// result = right_half(p, ); // // Description: // Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // x = The X coordinate of the cut-plane. Default: 0 -// planar = If true, this becomes a 2D operation. +// planar = If true perform a 2D operation. // // Examples(FlatSpin,VPD=175): // right_half() sphere(r=20); @@ -265,28 +273,26 @@ 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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[1,0,0], cp=x, p=p); +function right_half(p,x=0) = half_of(p, RIGHT, [x,0,0]); // Function&Module: front_half() // // Usage: -// front_half([s], [y]) ... -// front_half(planar=true, [s], [y]) ... +// front_half(, ) ... +// front_half(planar=true, , ) ... +// Usage: as function +// result = front_half(p, ); // // Description: // Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // y = The Y coordinate of the cut-plane. Default: 0 -// planar = If true, this becomes a 2D operation. +// planar = If true perform a 2D operation. // // Examples(FlatSpin,VPD=175): // front_half() sphere(r=20); @@ -307,28 +313,26 @@ 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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[0,-1,0], cp=x, p=p); +function front_half(p,y=0) = half_of(p, FRONT, [0,y,0]); // Function&Module: back_half() // // Usage: -// back_half([s], [y]) ... -// back_half(planar=true, [s], [y]) ... +// back_half(, ) ... +// back_half(planar=true, , ) ... +// Usage: as function +// result = back_half(p, ); // // Description: // Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // y = The Y coordinate of the cut-plane. Default: 0 -// planar = If true, this becomes a 2D operation. +// planar = If true perform a 2D operation. // // Examples: // back_half() sphere(r=20); @@ -349,24 +353,22 @@ 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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[0,1,0], cp=x, p=p); +function back_half(p,y=0) = half_of(p, BACK, [0,y,0]); // Function&Module: bottom_half() // // Usage: -// bottom_half([s], [z]) ... +// bottom_half(, ) ... +// Usage: as function +// result = bottom_half(p, ); // // Description: // Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // z = The Z coordinate of the cut-plane. Default: 0 // @@ -383,24 +385,21 @@ module bottom_half(s=1000, z=0) } } } -function bottom_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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[0,0,-1], cp=x, p=p); +function bottom_half(p,z=0) = half_of(p,BOTTOM,[0,0,z]); // Function&Module: top_half() // // Usage: -// top_half([s], [z]) ... +// top_half(, ) ... +// result = top_half(p, ); // // Description: // Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it. // // Arguments: +// p = VNF, region or path to slice (function version) // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000 // z = The Z coordinate of the cut-plane. Default: 0 // @@ -417,12 +416,7 @@ module top_half(s=1000, z=0) } } } -function top_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]]), - x=args[0], p=args[1], s=args[2]) - half_of(v=[0,0,1], cp=x, p=p); +function top_half(p,z=0) = half_of(p,UP,[0,0,z]); diff --git a/vnf.scad b/vnf.scad index 33b8598..c9ee06c 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1031,144 +1031,111 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { // Function: vnf_halfspace() // Usage: -// vnf_halfspace([a,b,c,d], vnf) +// newvnf = vnf_halfspace(plane, vnf, ); // Description: -// returns the intersection of the VNF with the given half-space. +// Returns the intersection of the vnf with a half space. The half space is defined by +// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. +// If closed is set to false then the cut face is not included in the vnf. This could +// allow further extension of the vnf by merging with other vnfs. // Arguments: -// halfspace = half-space to intersect with, given as the four coefficients of the affine inequation a\*x+b\*y+c\*z≥ d. +// plane = plane defining the boundary of the half space +// vnf = vnf to cut +// closed = if false do not return include cut face(s). Default: true +// Example: +// vnf = cube(10,center=true); +// cutvnf = vnf_halfspace([-1,1,-1,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut face has 2 components +// vnf = path_sweep(circle(r=4, $fn=16), +// circle(r=20, $fn=64),closed=true); +// cutvnf = vnf_halfspace([-1,1,-4,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut face is not simply connected +// vnf = path_sweep(circle(r=4, $fn=16), +// circle(r=20, $fn=64),closed=true); +// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut object has multiple components +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); +// ksteps = 400; +// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; +// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; +// knot=path_sweep(ushape, knot_path, closed=true, method="incremental"); +// cut_knot = vnf_halfspace([1,0,0,0], knot); +// vnf_polyhedron(cut_knot); +function vnf_halfspace(plane, vnf, closed=true) = + let( + inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0], + vertexmap = [0,each cumsum(inside)], + faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)), + newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2]) + ) + closed==false ? [newvert, faces_edges_vertices[0]] : + let( + allpaths = _assemble_paths(newvert, faces_edges_vertices[1]), + newpaths = [for(p=allpaths) if (len(p)>=3) p + else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.") + ] + ) + len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] + : + let( + faceregion = [for(p=newpaths) project_plane(select(newvert,p), plane)], + facevnf = region_faces(faceregion,reverse=true), + faceverts = lift_plane(facevnf[0], plane) + ) + vnf_merge([[newvert, faces_edges_vertices[0]], [faceverts, facevnf[1]]]); -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_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 = _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... - 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), - reg = [for(p=paths) project_plane(select(coords,p), halfspace)], - regvnf = region_faces(reg,reverse=true), - regvert = lift_plane(regvnf[0], halfspace) - //loops=[for(p=paths) if(coords[p[0]] == coords[last(p)]) reverse(p)]) - ) - vnf_merge([[coords, newfaces], [regvert, regvnf[1]]]); -// [coords, concat(newfaces, loops)]; + +function _assemble_paths(vertices, edges, paths=[],i=0) = + i==len(edges) ? paths : + norm(vertices[edges[i][0]]-vertices[edges[i][1]])3 + ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1, + concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1) + : + _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1); + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap From 405ccbcb058bb1d4fde327e2a325a169eefe84e0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 19 Apr 2021 22:04:45 +0000 Subject: [PATCH 4/4] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index 958d0a6..6d36f28 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,615]; +BOSL_VERSION = [2,0,616]; // Section: BOSL Library Version Functions