Merge pull request #326 from plut/plut_dev

Plut dev
This commit is contained in:
Revar Desmera 2020-12-04 17:12:06 -08:00 committed by GitHub
commit 00bf097c57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 338 additions and 12 deletions

View file

@ -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.

View file

@ -312,7 +312,96 @@ 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()
// 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 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 (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,< <named2>, 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` :
_undef="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI";
/* 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:
// scalar_vec3(v, <dflt>);

View file

@ -64,13 +64,20 @@ module bounding_box(excess=0) {
}
// Module: half_of()
// Function&Module: half_of()
//
// Usage:
// half_of(v, [cp], [s]) ...
// 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,12 +118,54 @@ 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
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])]) ]
:
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");
// Module: left_half()
// Function&Module: left_half()
//
// Usage:
// left_half([s], [x]) ...
// left_half(planar=true, [s], [x]) ...
// 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.
@ -145,15 +194,22 @@ 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);
// Module: right_half()
// Function&Module: right_half()
//
// Usage:
// 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.
//
@ -181,10 +237,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]]),
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]) ...
@ -217,10 +279,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]]),
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]) ...
@ -253,10 +321,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]]),
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]) ...
@ -281,10 +355,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]]),
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]) ...
@ -309,6 +389,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]]),
x=args[0], p=args[1], s=args[2])
half_of(v=[0,0,1], cp=x, p=p);

139
vnf.scad
View file

@ -827,5 +827,144 @@ 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.
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),
loops=[for(p=paths) if(p[0] == last(p)) p])
[coords, concat(newfaces, loops)];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap