This commit is contained in:
Adrian Mariano 2020-12-09 19:12:32 -05:00
commit cd7a0c31a2
30 changed files with 896 additions and 281 deletions

View file

@ -24,7 +24,7 @@ If you wish to contribute bugfixes or code to the BOSL2 project, the standard wa
3. Click the Clone button.
4. When it asks "How are you planning to use this fork?", click on the button "To contribute to the parent project."
1. Before you edit files, always syncronize with the upstream repository:
1. Before you edit files, always synchronize with the upstream repository:
- If using the command-line:
```
git pull upstream

View file

@ -486,7 +486,7 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][
// rot_decode(left(12)*xrot(-33)); // Returns [33, [-1,0,0], [0,0,0], [-12,0,0]]
// rot_decode(translate([3,4,5])); // Returns [0, [0,0,1], [0,0,0], [3,4,5]]
function rot_decode(M) =
assert(is_matrix(M,4,4) && M[3]==[0,0,0,1], "Input matrix must be a 4x4 matrix representing a 3d transformation")
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
let(R = submatrix(M,[0:2],[0:2]))
assert(approx(det3(R),1) && approx(norm_fro(R * transpose(R)-ident(3)),0),"Input matrix is not a rotation")
let(

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

@ -923,7 +923,8 @@ module attachable(
$parent_geom = geom;
$parent_size = attach_geom_size(geom);
$attach_to = undef;
if (attachment_is_shown($tags)) {
do_show = attachment_is_shown($tags);
if (do_show) {
if (is_undef($color)) {
children(0);
} else color($color) {
@ -1270,7 +1271,7 @@ module recolor(c)
// Usage:
// hide(tags) ...
// Description:
// Hides all children with the given tags.
// Hides all children with the given tags. Overrides any previous `hide()` or `show()` calls.
// Example:
// hide("A") cube(50, anchor=CENTER, $tags="Main") {
// attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A");
@ -1279,6 +1280,7 @@ module recolor(c)
module hide(tags="")
{
$tags_hidden = tags==""? [] : str_split(tags, " ");
$tags_shown = [];
children();
}
@ -1287,7 +1289,7 @@ module hide(tags="")
// Usage:
// show(tags) ...
// Description:
// Shows only children with the given tags.
// Shows only children with the given tags. Overrides any previous `hide()` or `show()` calls.
// Example:
// show("A B") cube(50, anchor=CENTER, $tags="Main") {
// attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A");
@ -1296,6 +1298,7 @@ module hide(tags="")
module show(tags="")
{
$tags_shown = tags==""? [] : str_split(tags, " ");
$tags_hidden = [];
children();
}
@ -1305,13 +1308,12 @@ module show(tags="")
// diff(neg, [keep]) ...
// diff(neg, pos, [keep]) ...
// Description:
// If `neg` is given, takes the union of all children with tags
// that are in `neg`, and differences them from the union of all
// children with tags in `pos`. If `pos` is not given, then all
// items in `neg` are differenced from all items not in `neg`. If
// `keep` is given, all children with tags in `keep` are then unioned
// with the result. If `keep` is not given, all children without
// tags in `pos` or `neg` are then unioned with the result.
// If `neg` is given, takes the union of all children with tags that are in `neg`, and differences
// them from the union of all children with tags in `pos`. If `pos` is not given, then all items in
// `neg` are differenced from all items not in `neg`. If `keep` is given, all children with tags in
// `keep` are then unioned with the result. If `keep` is not given, all children without tags in
// `pos` or `neg` are then unioned with the result.
// Cannot be used in conjunction with `intersect()` or `hulling()` on the same parent object.
// Arguments:
// neg = String containing space delimited set of tag names of children to difference away.
// pos = String containing space delimited set of tag names of children to be differenced away from.
@ -1364,14 +1366,12 @@ module diff(neg, pos=undef, keep=undef)
// intersect(a, [keep]) ...
// intersect(a, b, [keep]) ...
// Description:
// If `a` is given, takes the union of all children with tags that
// are in `a`, and intersection()s them with the union of all
// children with tags in `b`. If `b` is not given, then the union
// of all items with tags in `a` are intersection()ed with the union
// of all items without tags in `a`. If `keep` is given, then the
// result is unioned with all the children with tags in `keep`. If
// `keep` is not given, all children without tags in `a` or `b` are
// unioned with the result.
// If `a` is given, takes the union of all children with tags that are in `a`, and `intersection()`s
// them with the union of all children with tags in `b`. If `b` is not given, then the union of all
// items with tags in `a` are intersection()ed with the union of all items without tags in `a`. If
// `keep` is given, then the result is unioned with all the children with tags in `keep`. If `keep`
// is not given, all children without tags in `a` or `b` are unioned with the result.
// Cannot be used in conjunction with `diff()` or `hulling()` on the same parent object.
// Arguments:
// a = String containing space delimited set of tag names of children.
// b = String containing space delimited set of tag names of children.
@ -1410,15 +1410,14 @@ module intersect(a, b=undef, keep=undef)
// Module: hulling()
// Usage:
// hulling(a, [keep]) ...
// hulling(a) ...
// Description:
// Takes the union of all children with tags that are in `a`, and hull()s them.
// If `keep` is given, then the result is unioned with all the children with
// tags in `keep`. If `keep` is not given, all children without tags in `a` are
// unioned with the result.
// If `a` is not given, then all children are `hull()`ed together.
// If `a` is given as a string, then all children with `$tags` that are in `a` are
// `hull()`ed together and the result is then unioned with all the remaining children.
// Cannot be used in conjunction with `diff()` or `intersect()` on the same parent object.
// Arguments:
// a = String containing space delimited set of tag names of children.
// keep = String containing space delimited set of tag names of children to keep whole.
// a = String containing space delimited set of tag names of children to hull.
// Example:
// hulling("body")
// sphere(d=100, $tags="body") {
@ -1427,8 +1426,12 @@ module intersect(a, b=undef, keep=undef)
// }
module hulling(a)
{
hull() show(a) children();
children();
if (is_undef(a)) {
hull() children();
} else {
hull() show(a) children();
children();
}
}

View file

@ -9,10 +9,8 @@
//////////////////////////////////////////////////////////////////////
include <skin.scad>
// Section: Terminology
// **Polyline**: A series of points joined by straight line segements.
// **Path**: A series of points joined by straight line segements.
// .
// **Bezier Curve**: A mathematical curve that joins two endpoints, following a curve determined by one or more control points.
// .
@ -27,7 +25,7 @@ include <skin.scad>
// .
// **Bezier Path**: A list of bezier segments flattened out into a list of points, where each segment shares the endpoint of the previous segment as a start point. A cubic Bezier Path looks something like:
// `[endpt1, cp1, cp2, endpt2, cp3, cp4, endpt3]`
// **NOTE**: A bezier path is *NOT* a polyline. It is only the points and controls used to define the curve.
// **NOTE**: A "bezier path" is *NOT* a standard path. It is only the points and controls used to define the curve.
// .
// **Bezier Patch**: A surface defining grid of (N+1) by (N+1) bezier points. If a Bezier Segment defines a curved line, a Bezier Patch defines a curved surface.
// .
@ -374,7 +372,7 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) =
// p0 = [40, 0];
// p1 = [0, 0];
// p2 = [30, 30];
// trace_polyline([p0,p1,p2], showpts=true, size=0.5, color="green");
// trace_path([p0,p1,p2], showpts=true, size=0.5, color="green");
// fbez = fillet3pts(p0,p1,p2, 10);
// trace_bezier(slice(fbez, 1, -2), size=1);
function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let(
@ -482,11 +480,11 @@ function bezier_path_length(path, N=3, max_deflect=0.001) =
// Function: bezier_polyline()
// Function: bezier_path()
// Usage:
// bezier_polyline(bezier, [splinesteps], [N])
// bezier_path(bezier, [splinesteps], [N])
// Description:
// Takes a bezier path and converts it into a polyline.
// Takes a bezier path and converts it into a path of points.
// Arguments:
// bezier = A bezier path to approximate.
// splinesteps = Number of straight lines to split each bezier segment into. default=16
@ -498,9 +496,9 @@ function bezier_path_length(path, N=3, max_deflect=0.001) =
// [60,25], [70,0], [80,-25],
// [80,-50], [50,-50]
// ];
// trace_polyline(bez, size=1, N=3, showpts=true);
// trace_polyline(bezier_polyline(bez, N=3), size=3);
function bezier_polyline(bezier, splinesteps=16, N=3) =
// trace_path(bez, size=1, N=3, showpts=true);
// trace_path(bezier_path(bez, N=3), size=3);
function bezier_path(bezier, splinesteps=16, N=3) =
assert(is_path(bezier))
assert(is_int(N))
assert(is_int(splinesteps))
@ -598,15 +596,15 @@ function path_to_bezier(path, tangents, size, relsize, uniform=false, closed=fal
// Usage:
// fillet_path(pts, fillet, [maxerr]);
// Description:
// Takes a 3D polyline path and fillets the corners, returning a 3d cubic (degree 3) bezier path.
// Takes a 3D path and fillets the corners, returning a 3d cubic (degree 3) bezier path.
// Arguments:
// pts = 3D Polyline path to fillet.
// fillet = The radius to fillet/round the polyline corners by.
// pts = 3D path to fillet.
// fillet = The radius to fillet/round the path corners by.
// maxerr = Max amount bezier curve should diverge from actual radius curve. Default: 0.1
// Example(2D):
// pline = [[40,0], [0,0], [35,35], [0,70], [-10,60], [-5,55], [0,60]];
// bez = fillet_path(pline, 10);
// trace_polyline(pline, showpts=true, size=0.5, color="green");
// trace_path(pline, showpts=true, size=0.5, color="green");
// trace_bezier(bez, size=1);
function fillet_path(pts, fillet, maxerr=0.1) = concat(
[pts[0], pts[0]],
@ -722,7 +720,7 @@ module bezier_polygon(bezier, splinesteps=16, N=3) {
assert(is_int(N));
assert(is_int(splinesteps));
assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1."));
polypoints=bezier_polyline(bezier, splinesteps, N);
polypoints=bezier_path(bezier, splinesteps, N);
polygon(points=slice(polypoints, 0, -1));
}
@ -764,8 +762,10 @@ module linear_sweep_bezier(bezier, height=100, splinesteps=16, N=3, center, conv
maxy = max([for (pt = bezier) abs(pt[1])]);
anchor = get_anchor(anchor,center,BOT,BOT);
attachable(anchor,spin,orient, size=[maxx*2,maxy*2,height]) {
linear_extrude(height=height, center=true, convexity=convexity, twist=twist, slices=slices, scale=scale) {
bezier_polygon(bezier, splinesteps=splinesteps, N=N);
if (height > 0) {
linear_extrude(height=height, center=true, convexity=convexity, twist=twist, slices=slices, scale=scale) {
bezier_polygon(bezier, splinesteps=splinesteps, N=N);
}
}
children();
}
@ -803,7 +803,7 @@ module rotate_sweep_bezier(bezier, splinesteps=16, N=3, convexity=undef, angle=3
assert(is_int(N));
assert(is_num(angle));
assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1."));
oline = bezier_polyline(bezier, splinesteps=splinesteps, N=N);
oline = bezier_path(bezier, splinesteps=splinesteps, N=N);
maxx = max([for (pt = oline) abs(pt[0])]);
miny = min(subindex(oline,1));
maxy = max(subindex(oline,1));
@ -839,7 +839,7 @@ module bezier_path_extrude(bezier, splinesteps=16, N=3, convexity=undef, clipsiz
assert(is_int(N));
assert(is_num(clipsize));
assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1."));
path = slice(bezier_polyline(bezier, splinesteps, N), 0, -1);
path = slice(bezier_path(bezier, splinesteps, N), 0, -1);
path_extrude(path, convexity=convexity, clipsize=clipsize) children();
}
@ -876,8 +876,8 @@ module bezier_sweep_bezier(bezier, path, pathsteps=16, bezsteps=16, bezN=3, path
assert(is_int(pathN));
assert(len(bezier)%bezN == 1, str("For argument bezier, a degree ",bezN," bezier path shound have a multiple of ",bezN," points in it, plus 1."));
assert(len(path)%pathN == 1, str("For argument bezier, a degree ",pathN," bezier path shound have a multiple of ",pathN," points in it, plus 1."));
bez_points = simplify_path(bezier_polyline(bezier, bezsteps, bezN));
path_points = simplify_path(path3d(bezier_polyline(path, pathsteps, pathN)));
bez_points = simplify_path(bezier_path(bezier, bezsteps, bezN));
path_points = simplify_path(path3d(bezier_path(path, pathsteps, pathN)));
path_sweep(bez_points, path_points);
}
@ -902,8 +902,8 @@ module trace_bezier(bez, N=3, size=1) {
assert(is_path(bez));
assert(is_int(N));
assert(len(bez)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1."));
trace_polyline(bez, N=N, showpts=true, size=size, color="green");
trace_polyline(bezier_polyline(bez, N=N), size=size, color="cyan");
trace_path(bez, N=N, showpts=true, size=size, color="green");
trace_path(bezier_path(bez, N=N), size=size, color="cyan");
}

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

@ -8,32 +8,30 @@
// ```
//////////////////////////////////////////////////////////////////////
include <skin.scad>
// Section: Debugging Paths and Polygons
// Module: trace_polyline()
// Module: trace_path()
// Description:
// Renders lines between each point of a polyline path.
// Renders lines between each point of a path.
// Can also optionally show the individual vertex points.
// Arguments:
// pline = The array of points in the polyline.
// path = The list of points in the path.
// closed = If true, draw the segment from the last vertex to the first. Default: false
// showpts = If true, draw vertices and control points.
// N = Mark the first and every Nth vertex after in a different color and shape.
// size = Diameter of the lines drawn.
// color = Color to draw the lines (but not vertices) in.
// Example(FlatSpin):
// polyline = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]];
// trace_polyline(polyline, showpts=true, size=0.5, color="lightgreen");
module trace_polyline(pline, closed=false, showpts=false, N=1, size=1, color="yellow") {
assert(is_path(pline),"Input pline is not a path");
// path = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]];
// trace_path(path, showpts=true, size=0.5, color="lightgreen");
module trace_path(path, closed=false, showpts=false, N=1, size=1, color="yellow") {
assert(is_path(path),"Invalid path argument");
sides = segs(size/2);
pline = closed? close_path(pline) : pline;
path = closed? close_path(path) : path;
if (showpts) {
for (i = [0:1:len(pline)-1]) {
translate(pline[i]) {
for (i = [0:1:len(path)-1]) {
translate(path[i]) {
if (i%N == 0) {
color("blue") sphere(d=size*2.5, $fn=8);
} else {
@ -47,11 +45,11 @@ module trace_polyline(pline, closed=false, showpts=false, N=1, size=1, color="ye
}
}
if (N!=3) {
color(color) stroke(path3d(pline), width=size, $fn=8);
color(color) stroke(path3d(path), width=size, $fn=8);
} else {
for (i = [0:1:len(pline)-2]) {
for (i = [0:1:len(path)-2]) {
if (N!=3 || (i%N) != 1) {
color(color) extrude_from_to(pline[i], pline[i+1]) circle(d=size, $fn=sides);
color(color) extrude_from_to(path[i], path[i+1]) circle(d=size, $fn=sides);
}
}
}
@ -254,11 +252,19 @@ module debug_polyhedron(points, faces, convexity=10, txtsize=1, disabled=false)
// Function: standard_anchors()
// Usage:
// anchs = standard_anchors(<two_d>);
// Description:
// Return the vectors for all standard anchors.
function standard_anchors() = [
// Arguments:
// two_d = If true, returns only the anchors where the Z component is 0. Default: false
function standard_anchors(two_d=false) = [
for (
zv = [TOP, CENTER, BOTTOM],
zv = [
if (!two_d) TOP,
CENTER,
if (!two_d) BOTTOM
],
yv = [FRONT, CENTER, BACK],
xv = [LEFT, CENTER, RIGHT]
) xv+yv+zv
@ -448,5 +454,35 @@ module ruler(length=100, width=undef, thickness=1, depth=3, labels=false, pipsca
}
// Function: mod_indent()
// Usage:
// str = mod_indent(<indent>);
// Description:
// Returns a string that is the total indentation for the module level you are at.
// Arguments:
// indent = The string to indent each level by. Default: " " (Two spaces)
// Example:
// x = echo(str(mod_indent(), parent_module(0)));
function mod_indent(indent=" ") =
str_join([for (i=[1:1:$parent_modules-1]) indent]);
// Function: mod_trace()
// Usage:
// str = mod_trace(<levs>, <indent>);
// Description:
// Returns a string that shows the current module and its parents, indented for each unprinted parent module.
// Arguments:
// levs = This is the number of levels to print the names of. Prints the N most nested module names. Default: 2
// indent = The string to indent each level by. Default: " " (Two spaces)
// modsep = Multiple module names will be separated by this string. Default: "->"
// Example:
// x = echo(mod_trace());
function mod_trace(levs=2, indent=" ", modsep="->") =
str(
str_join([for (i=[1:1:$parent_modules+1-levs]) indent]),
str_join([for (i=[min(levs-1,$parent_modules-1):-1:0]) parent_module(i)], modsep)
);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -152,7 +152,7 @@ module line_of(spacing, n, l, p1, p2)
// spacing = spacing between copies. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given, copies will be spread on a line to the right of starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
// sp = If given as a point, copies will be spread on a line to the right of starting position `sp`. If given as a scalar, copies will be spread on a line to the right of starting position `[sp,0,0]`. If not given, copies will be spread along a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -170,6 +170,7 @@ module line_of(spacing, n, l, p1, p2)
// }
module xcopies(spacing, n, l, sp)
{
sp = is_finite(sp)? [sp,0,0] : sp;
line_of(l=l*RIGHT, spacing=spacing*RIGHT, n=n, p1=sp) children();
}
@ -187,7 +188,7 @@ module xcopies(spacing, n, l, sp)
// spacing = spacing between copies. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given, copies will be spread on a line back from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
// sp = If given as a point, copies will be spread on a line back from starting position `sp`. If given as a scalar, copies will be spread on a line back from starting position `[0,sp,0]`. If not given, copies will be spread along a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -205,6 +206,7 @@ module xcopies(spacing, n, l, sp)
// }
module ycopies(spacing, n, l, sp)
{
sp = is_finite(sp)? [0,sp,0] : sp;
line_of(l=l*BACK, spacing=spacing*BACK, n=n, p1=sp) children();
}
@ -222,7 +224,7 @@ module ycopies(spacing, n, l, sp)
// spacing = spacing between copies. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given, copies will be spread on a line up from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
// sp = If given as a point, copies will be spread on a line up from starting position `sp`. If given as a scalar, copies will be spread on a line up from starting position `[0,0,sp]`. If not given, copies will be spread along a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -240,6 +242,7 @@ module ycopies(spacing, n, l, sp)
// }
module zcopies(spacing, n, l, sp)
{
sp = is_finite(sp)? [0,0,sp] : sp;
line_of(l=l*UP, spacing=spacing*UP, n=n, p1=sp) children();
}

View file

@ -2005,10 +2005,11 @@ function _split_polygon_at_x(poly, x) =
],
out1 = [for (p = poly2) if(p.x <= x) p],
out2 = [for (p = poly2) if(p.x >= x) p],
out = [
if (len(out1)>=3) out1,
if (len(out2)>=3) out2,
]
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
@ -2034,10 +2035,11 @@ function _split_polygon_at_y(poly, y) =
],
out1 = [for (p = poly2) if(p.y <= y) p],
out2 = [for (p = poly2) if(p.y >= y) p],
out = [
if (len(out1)>=3) out1,
if (len(out2)>=3) out2,
]
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
@ -2063,10 +2065,11 @@ function _split_polygon_at_z(poly, z) =
],
out1 = [for (p = poly2) if(p.z <= z) p],
out2 = [for (p = poly2) if(p.z >= z) p],
out = [
if (len(out1)>=3) out1,
if (len(out2)>=3) out2,
]
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(close_path(out1), closed=false),
if (len(out2)>=3) each split_path_at_self_crossings(close_path(out2), closed=false),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;

View file

@ -10,7 +10,6 @@
include <rounding.scad>
include <skin.scad>
// Section: Half Joiners
@ -989,7 +988,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1
: let(side_smooth=select(pin_smooth, 0, 2))
concat(side_smooth, [socket_smooth], reverse(side_smooth));
bez = path_to_bezier(path,relsize=smoothing,tangents=tangent);
rounded = bezier_polyline(bez,splinesteps=splinesteps);
rounded = bezier_path(bez,splinesteps=splinesteps);
bounds = pointlist_bounds(rounded);
//kk = search([bounds[1].y], subindex(rounded,1));
//echo(rounded[kk[0]]);

View file

@ -617,6 +617,44 @@ function _product(v, i=0, _tot) =
// Function: cumprod()
// Description:
// Returns a list where each item is the cumulative product of all items up to and including the corresponding entry in the input list.
// If passed an array of vectors, returns a list of elementwise vector products. If passed a list of square matrices returns matrix
// products multiplying in the order items appear in the list.
// Arguments:
// list = The list to get the product of.
// Example:
// cumprod([1,3,5]); // returns [1,3,15]
// cumprod([2,2,2]); // returns [2,4,8]
// cumprod([[1,2,3], [3,4,5], [5,6,7]])); // returns [[1, 2, 3], [3, 8, 15], [15, 48, 105]]
function cumprod(list) =
is_vector(list) ? _cumprod(list) :
assert(is_consistent(list), "Input must be a consistent list of scalars, vectors or square matrices")
is_matrix(list[0]) ? assert(len(list[0])==len(list[0][0]), "Matrices must be square") _cumprod(list)
: _cumprod_vec(list);
function _cumprod(v,_i=0,_acc=[]) =
_i==len(v) ? _acc :
_cumprod(
v, _i+1,
concat(
_acc,
[_i==0 ? v[_i] : _acc[len(_acc)-1]*v[_i]]
)
);
function _cumprod_vec(v,_i=0,_acc=[]) =
_i==len(v) ? _acc :
_cumprod_vec(
v, _i+1,
concat(
_acc,
[_i==0 ? v[_i] : vmul(_acc[len(_acc)-1],v[_i])]
)
);
// Function: outer_product()
// Usage:
// x = outer_product(u,v);

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);

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////
// LibFile: paths.scad
// Polylines, polygons and paths.
// Support for polygons and paths.
// To use, add the following lines to the beginning of your file:
// ```
// include <BOSL2/std.scad>
@ -421,7 +421,7 @@ function path_torsion(path, closed=false) =
// cp = Centerpoint of spiral. Default: `[0,0]`
// scale = [X,Y] scaling factors for each axis. Default: `[1,1]`
// Example(3D):
// trace_polyline(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
// trace_path(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let(
rr=get_radius(r=r, d=d, dflt=100),
cnt=floor(turns*n),
@ -435,44 +435,6 @@ function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let(
];
// Function: points_along_path3d()
// Usage:
// points_along_path3d(polyline, path);
// Description:
// Calculates the vertices needed to create a `polyhedron()` of the
// extrusion of `polyline` along `path`. The closed 2D path shold be
// centered on the XY plane. The 2D path is extruded perpendicularly
// along the 3D path. Produces a list of 3D vertices. Vertex count
// is `len(polyline)*len(path)`. Gives all the reoriented vertices
// for `polyline` at the first point in `path`, then for the second,
// and so on.
// Arguments:
// polyline = A closed list of 2D path points.
// path = A list of 3D path points.
function points_along_path3d(
polyline, // The 2D polyline to drag along the 3D path.
path, // The 3D polyline path to follow.
q=Q_Ident(), // Used in recursion
n=0 // Used in recursion
) = let(
end = len(path)-1,
v1 = (n == 0)? [0, 0, 1] : unit(path[n]-path[n-1]),
v2 = (n == end)? unit(path[n]-path[n-1]) : unit(path[n+1]-path[n]),
crs = cross(v1, v2),
axis = norm(crs) <= 0.001? [0, 0, 1] : crs,
ang = vector_angle(v1, v2),
hang = ang * (n==0? 1.0 : 0.5),
hrot = Quat(axis, hang),
arot = Quat(axis, ang),
roth = Q_Mul(hrot, q),
rotm = Q_Mul(arot, q)
) concat(
[for (i = [0:1:len(polyline)-1]) Qrot(roth,p=point3d(polyline[i])) + path[n]],
(n == end)? [] : points_along_path3d(polyline, path, rotm, n+1)
);
// Function: path_self_intersections()
// Usage:
// isects = path_self_intersections(path, [eps]);
@ -529,9 +491,9 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
// Function: split_path_at_self_crossings()
// Usage:
// polylines = split_path_at_self_crossings(path, [closed], [eps]);
// paths = split_path_at_self_crossings(path, [closed], [eps]);
// Description:
// Splits a path into polyline sections wherever the path crosses itself.
// Splits a path into sub-paths wherever the original path crosses itself.
// Splits may occur mid-segment, so new vertices will be created at the intersection points.
// Arguments:
// path = The path to split up.
@ -539,8 +501,8 @@ function path_self_intersections(path, closed=true, eps=EPSILON) =
// eps = Acceptable variance. Default: `EPSILON` (1e-9)
// Example(2D):
// path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ];
// polylines = split_path_at_self_crossings(path);
// rainbow(polylines) stroke($item, closed=false, width=2);
// paths = split_path_at_self_crossings(path);
// rainbow(paths) stroke($item, closed=false, width=2);
function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
let(
path = cleanup_path(path, eps=eps),
@ -681,11 +643,11 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) =
// Usage:
// assemble_a_path_from_fragments(subpaths);
// Description:
// Given a list of incomplete paths, assembles them together into one complete closed path, and
// Given a list of paths, assembles them together into one complete closed polygon path, and
// remainder fragments. Returns [PATH, FRAGMENTS] where FRAGMENTS is the list of remaining
// polyline path fragments.
// unused path fragments.
// Arguments:
// fragments = List of polylines to be assembled into complete polygons.
// fragments = List of paths to be assembled into complete polygons.
// rightmost = If true, assemble paths using rightmost turns. Leftmost if false.
// startfrag = The fragment to start with. Default: 0
// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
@ -738,9 +700,9 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
// Usage:
// assemble_path_fragments(subpaths);
// Description:
// Given a list of incomplete paths, assembles them together into complete closed paths if it can.
// Given a list of paths, assembles them together into complete closed polygon paths if it can.
// Arguments:
// fragments = List of polylines to be assembled into complete polygons.
// fragments = List of paths to be assembled into complete polygons.
// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
function assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) =
len(fragments)==0? _finished :
@ -785,16 +747,20 @@ function assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) =
// Arguments:
// r = Radius of the base circle. Default: 40
// d = Diameter of the base circle.
// sines = array of [amplitude, frequency] pairs, where the frequency is the number of times the cycle repeats around the circle.
// sines = array of [amplitude, frequency] pairs or [amplitude, frequency, phase] triples, where the frequency is the number of times the cycle repeats around the circle.
// Example(2D):
// modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6);
module modulated_circle(r, sines=[10], d)
module modulated_circle(r, sines=[[1,1]], d)
{
r = get_radius(r=r, d=d, dflt=40);
freqs = len(sines)>0? [for (i=sines) i[1]] : [5];
assert(is_list(sines)
&& all([for(s=sines) is_vector(s,2) || is_vector(s,3)]),
"sines must be given as a list of pairs or triples");
sines_ = [for(s=sines) [s[0], s[1], len(s)==2 ? 0 : s[2]]];
freqs = len(sines_)>0? [for (i=sines_) i[1]] : [5];
points = [
for (a = [0 : (360/segs(r)/max(freqs)) : 360])
let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)]
let(nr=r+sum_of_sines(a,sines_)) [nr*cos(a), nr*sin(a)]
];
polygon(points);
}
@ -817,12 +783,14 @@ module modulated_circle(r, sines=[10], d)
// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
// xcopies(3) circle(3, $fn=32);
// }
module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slices=undef) {
module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
rtp = xyz_to_spherical(pt2-pt1);
translate(pt1) {
rotate([0, rtp[2], rtp[1]]) {
linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
children();
if (rtp[0] > 0) {
linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
children();
}
}
}
}
@ -832,10 +800,10 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic
// Module: spiral_sweep()
// Description:
// Takes a closed 2D polyline path, centered on the XY plane, and
// extrudes it along a 3D spiral path of a given radius, height and twist.
// Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path
// of a given radius, height and twist.
// Arguments:
// polyline = Array of points of a polyline path, to be extruded.
// path = Array of points of a polygon path, to be extruded.
// h = height of the spiral to extrude along.
// r = Radius of the spiral to extrude along. Default: 50
// d = Diameter of the spiral to extrude along.
@ -847,10 +815,10 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic
// Example:
// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]];
// spiral_sweep(poly, h=200, r=50, twist=1080, $fn=36);
module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient=UP) {
module spiral_sweep(poly, h, r, twist=360, center, d, anchor, spin=0, orient=UP) {
r = get_radius(r=r, d=d, dflt=50);
polyline = path3d(polyline);
pline_count = len(polyline);
poly = path3d(poly);
pline_count = len(poly);
steps = ceil(segs(r)*(twist/360));
anchor = get_anchor(anchor,center,BOT,BOT);
@ -863,7 +831,7 @@ module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient
dy = r*sin(a),
dz = h * (p/steps),
pts = apply_list(
polyline, [
poly, [
affine3d_xrot(90),
affine3d_zrot(a),
affine3d_translate([dx, dy, dz-h/2])
@ -902,7 +870,7 @@ module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient
// Module: path_extrude()
// Description:
// Extrudes 2D children along a 3D polyline path. This may be slow.
// Extrudes 2D children along a 3D path. This may be slow.
// Arguments:
// path = array of points for the bezier path to extrude along.
// convexity = maximum number of walls a ran can pass through.
@ -933,8 +901,10 @@ module path_extrude(path, convexity=10, clipsize=100) {
translate(pt1) {
Qrot(q) {
down(clipsize/2/2) {
linear_extrude(height=dist+clipsize/2, convexity=convexity) {
children();
if ((dist+clipsize/2) > 0) {
linear_extrude(height=dist+clipsize/2, convexity=convexity) {
children();
}
}
}
}

View file

@ -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])
)

View file

@ -108,8 +108,10 @@ module cube(size=1, center, anchor, spin=0, orient=UP)
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
size = scalar_vec3(size);
attachable(anchor,spin,orient, size=size) {
linear_extrude(height=size.z, center=true, convexity=2) {
square([size.x,size.y], center=true);
if (size.z > 0) {
linear_extrude(height=size.z, center=true, convexity=2) {
square([size.x,size.y], center=true);
}
}
children();
}
@ -189,14 +191,18 @@ module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
l = first_defined([h, l, 1]);
sides = segs(max(r1,r2));
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
if(r1>r2) {
linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) {
circle(r=r1);
if (r1 > r2) {
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) {
circle(r=r1);
}
}
} else {
zflip() {
linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) {
circle(r=r2);
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) {
circle(r=r2);
}
}
}
}

View file

@ -154,9 +154,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
// Function: split_path_at_region_crossings()
// Usage:
// polylines = split_path_at_region_crossings(path, region, [eps]);
// paths = split_path_at_region_crossings(path, region, [eps]);
// Description:
// Splits a path into polyline sections wherever the path crosses the perimeter of a region.
// Splits a path into sub-paths wherever the path crosses the perimeter of a region.
// Splits may occur mid-segment, so new vertices will be created at the intersection points.
// Arguments:
// path = The path to split up.
@ -166,9 +166,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
// Example(2D):
// path = square(50,center=false);
// region = [circle(d=80), circle(d=40)];
// polylines = split_path_at_region_crossings(path, region);
// paths = split_path_at_region_crossings(path, region);
// color("#aaa") region(region);
// rainbow(polylines) stroke($item, closed=false, width=2);
// rainbow(paths) stroke($item, closed=false, width=2);
function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON) =
let(
path = deduplicate(path, eps=eps),
@ -285,8 +285,8 @@ function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
vnfs = [
if (vnf != EMPTY_VNF) vnf,
for (rgn = regions) let(
cleaved = _cleave_simple_region(rgn),
face = is_undef(transform)? cleaved : apply(transform,path3d(cleaved)),
cleaved = path3d(_cleave_simple_region(rgn)),
face = is_undef(transform)? cleaved : apply(transform,cleaved),
faceidxs = reverse? [for (i=[len(face)-1:-1:0]) i] : [for (i=[0:1:len(face)-1]) i]
) [face, [faceidxs]]
],

View file

@ -181,7 +181,6 @@ include <structs.scad>
// path_sweep(regular_ngon(n=36,or=.1),round_corners(list2,closed=false, method="circle", cut = 0.75));
// Example(FlatSpin): Rounding a spiral with increased rounding along the length
// // Construct a square spiral path in 3D
// include <BOSL2/skin.scad>
// $fn=36;
// square = [[0,0],[1,0],[1,1],[0,1]];
// spiral = flatten(repeat(concat(square,reverse(square)),5)); // Squares repeat 10 times, forward and backward
@ -454,7 +453,7 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
let (
bez = path_to_bezier(path, tangents=tangents, size=size, relsize=relsize, uniform=uniform, closed=closed)
)
bezier_polyline(bez,splinesteps=splinesteps);
bezier_path(bez,splinesteps=splinesteps);

View file

@ -78,14 +78,15 @@ module cuboid(
cnt = sum(e);
r = first_defined([chamfer, rounding, 0]);
c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)];
c2 = vmul(corner,c/2);
$fn = is_finite(chamfer)? 4 : segs(r);
translate(vmul(corner,size/2-c)) {
if (cnt == 0) {
cube(c*2, center=true);
translate(vmul(corner, size/2-c)) {
if (cnt == 0 || approx(r,0)) {
translate(c2) cube(c, center=true);
} else if (cnt == 1) {
if (e.x) xcyl(l=c.x*2, r=r);
if (e.y) ycyl(l=c.y*2, r=r);
if (e.z) zcyl(l=c.z*2, r=r);
if (e.x) right(c2.x) xcyl(l=c.x, r=r);
if (e.y) back (c2.y) ycyl(l=c.y, r=r);
if (e.z) up (c2.z) zcyl(l=c.z, r=r);
} else if (cnt == 2) {
if (!e.x) {
intersection() {
@ -119,6 +120,12 @@ module cuboid(
size = scalar_vec3(size);
edges = edges(edges, except=except_edges);
assert(is_vector(size,3));
assert(is_undef(chamfer) || is_finite(chamfer));
assert(is_undef(rounding) || is_finite(rounding));
assert(is_undef(p1) || is_vector(p1));
assert(is_undef(p2) || is_vector(p2));
assert(is_bool(trimcorners));
if (!is_undef(p1)) {
if (!is_undef(p2)) {
translate(pointlist_bounds([p1,p2])[0]) {
@ -130,19 +137,19 @@ module cuboid(
}
}
} else {
if (chamfer != undef) {
if (is_finite(chamfer)) {
if (any(edges[0])) assert(chamfer <= size.y/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube length or height.");
if (any(edges[1])) assert(chamfer <= size.x/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube width or height.");
if (any(edges[2])) assert(chamfer <= size.x/2 && chamfer <=size.y/2, "chamfer must be smaller than half the cube width or length.");
}
if (rounding != undef) {
if (is_finite(rounding)) {
if (any(edges[0])) assert(rounding <= size.y/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube length or height.");
if (any(edges[1])) assert(rounding <= size.x/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube width or height.");
if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length.");
}
majrots = [[0,90,0], [90,0,0], [0,0,0]];
attachable(anchor,spin,orient, size=size) {
if (chamfer != undef) {
if (is_finite(chamfer) && !approx(chamfer,0)) {
if (edges == EDGES_ALL && trimcorners) {
if (chamfer<0) {
cube(size, center=true) {
@ -152,9 +159,9 @@ module cuboid(
} else {
isize = [for (v = size) max(0.001, v-2*chamfer)];
hull() {
cube([size.x, isize.y, isize.z], center=true);
cube([isize.x, size.y, isize.z], center=true);
cube([isize.x, isize.y, size.z], center=true);
cube([ size.x, isize.y, isize.z], center=true);
cube([isize.x, size.y, isize.z], center=true);
cube([isize.x, isize.y, size.z], center=true);
}
}
} else if (chamfer<0) {
@ -211,7 +218,7 @@ module cuboid(
corner_shape([ 1, 1, 1]);
}
}
} else if (rounding != undef) {
} else if (is_finite(rounding) && !approx(rounding,0)) {
sides = quantup(segs(rounding),4);
if (edges == EDGES_ALL) {
if(rounding<0) {
@ -505,8 +512,10 @@ module right_triangle(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
size = scalar_vec3(size);
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
attachable(anchor,spin,orient, size=size) {
linear_extrude(height=size.z, convexity=2, center=true) {
polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]);
if (size.z > 0) {
linear_extrude(height=size.z, convexity=2, center=true) {
polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]);
}
}
children();
}
@ -893,11 +902,24 @@ module tube(
anchor, spin=0, orient=UP,
center, realign=false, l
) {
function safe_add(x,wall) = is_undef(x)? undef : x+wall;
h = first_defined([h,l,1]);
r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]);
r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]);
ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]);
ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]);
orr1 = get_radius(
r=first_defined([or1, r1, or, r]),
d=first_defined([od1, d1, od, d]),
dflt=undef
);
orr2 = get_radius(
r=first_defined([or2, r2, or, r]),
d=first_defined([od2, d2, od, d]),
dflt=undef
);
irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
r1 = is_num(orr1)? orr1 : is_num(irr1)? irr1+wall : undef;
r2 = is_num(orr2)? orr2 : is_num(irr2)? irr2+wall : undef;
ir1 = is_num(irr1)? irr1 : is_num(orr1)? orr1-wall : undef;
ir2 = is_num(irr2)? irr2 : is_num(orr2)? orr2-wall : undef;
assert(ir1 <= r1, "Inner radius is larger than outer radius.");
assert(ir2 <= r2, "Inner radius is larger than outer radius.");
sides = segs(max(r1,r2));
@ -1375,8 +1397,10 @@ module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, anchor=
size = [r*2,l,r*2];
attachable(anchor,spin,orient, size=size) {
rot(from=UP,to=FWD) {
linear_extrude(height=l, center=true, slices=2) {
teardrop2d(r=r, ang=ang, cap_h=cap_h);
if (l > 0) {
linear_extrude(height=l, center=true, slices=2) {
teardrop2d(r=r, ang=ang, cap_h=cap_h);
}
}
}
children();
@ -1547,12 +1571,14 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=FRONT+LEFT, spi
steps = ceil(segs(r)*ang/360);
step = ang/steps;
attachable(anchor,spin,orient, size=[r,r,l]) {
linear_extrude(height=l, convexity=4, center=true) {
path = concat(
[[0,0]],
[for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]]
);
translate(-[r,r]/2) polygon(path);
if (l > 0) {
linear_extrude(height=l, convexity=4, center=true) {
path = concat(
[[0,0]],
[for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]]
);
translate(-[r,r]/2) polygon(path);
}
}
children();
}

View file

@ -298,7 +298,7 @@ module stroke(
}
} else {
rotate([90,0,endcap_angle1]) {
linear_extrude(height=widths[0], center=true, convexity=convexity) {
linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) {
polygon(endcap_shape1);
}
}
@ -318,7 +318,7 @@ module stroke(
}
} else {
rotate([90,0,endcap_angle2]) {
linear_extrude(height=select(widths,-1), center=true, convexity=convexity) {
linear_extrude(height=max(select(widths,-1),0.001), center=true, convexity=convexity) {
polygon(endcap_shape2);
}
}
@ -377,7 +377,7 @@ module stroke(
// stroke(closed=true, path);
// Example(FlatSpin):
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
// trace_polyline(path, showpts=true, color="cyan");
// trace_path(path, showpts=true, color="cyan");
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false) =
// First try for 2D arc specified by width and thickness
is_def(width) && is_def(thickness)? (

181
skin.scad
View file

@ -10,7 +10,6 @@
// - https://github.com/openscad/list-comprehension-demos/blob/master/skin.scad
//////////////////////////////////////////////////////////////////////
include <vnf.scad>
// Section: Skinning
@ -824,11 +823,11 @@ function associate_vertices(polygons, split, curpoly=0) =
// Function&Module: sweep()
// Usage: As Module
// sweep(shape, transformations, <closed<, <caps>)
// sweep(shape, transforms, <closed>, <caps>)
// Usage: As Function
// vnf = sweep(shape, transformations, <closed>, <caps>);
// vnf = sweep(shape, transforms, <closed>, <caps>);
// Description:
// The input `shape` must be a non-self-intersecting polygon in two dimensions, and `transformations`
// The input `shape` must be a non-self-intersecting 2D polygon or region, and `transforms`
// is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence
// to the shape input and links the resulting polygons together to form a polyhedron.
// If `closed=true` then the first and last transformation are linked together.
@ -842,8 +841,8 @@ function associate_vertices(polygons, split, curpoly=0) =
// in your model, but will arise if you add a second object to the model. This may mislead you into
// thinking the second object caused a problem. Even adding a simple cube to the model will reveal the problem.
// Arguments:
// shape = 2d path describing shape to be swept
// transformations = list of 4x4 matrices to apply
// shape = 2d path or region, describing the shape to be swept.
// transforms = list of 4x4 matrices to apply
// closed = set to true to form a closed (torus) model. Default: false
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false.
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
@ -873,25 +872,39 @@ function associate_vertices(polygons, split, curpoly=0) =
// inside = [for(i=[24:-1:2]) up(i)*rot(i)*scale(1.2*i/24+1)];
// sweep(shape, concat(outside,inside));
function sweep(shape, transformations, closed=false, caps) =
assert(is_list_of(transformations, ident(4)), "Input transformations must be a list of numeric 4x4 matrices in sweep")
assert(is_path(shape,2), "Input shape must be a 2d path")
let(
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
fullcaps = is_bool(caps) ? [caps,caps] : caps
)
assert(len(transformations), "transformation must be length 2 or more")
assert(len(shape)>=3, "shape must be a path of at least 3 points")
assert(capsOK, "caps must be boolean or a list of two booleans")
assert(!closed || !caps, "Cannot make closed shape with caps")
_skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps);
function sweep(shape, transforms, closed=false, caps) =
assert(is_list_of(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep")
assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.")
let(
caps = is_def(caps) ? caps :
closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
fullcaps = is_bool(caps) ? [caps,caps] : caps
)
assert(len(transforms), "transformation must be length 2 or more")
assert(capsOK, "caps must be boolean or a list of two booleans")
assert(!closed || !caps, "Cannot make closed shape with caps")
is_region(shape)? let(
regions = split_nested_region(shape),
rtrans = reverse(transforms),
vnfs = [
for (rgn=regions) each [
for (path=select(rgn,0,-1))
sweep(path, transforms, closed=closed, caps=false),
if (fullcaps[0]) region_faces(rgn, transform=transforms[0], reverse=true),
if (fullcaps[1]) region_faces(rgn, transform=select(transforms,-1)),
],
],
vnf = vnf_merge(vnfs)
) vnf :
assert(len(shape)>=3, "shape must be a path of at least 3 non-colinear points")
_skin_core([for(i=[0:len(transforms)-(closed?0:1)]) apply(transforms[i%len(transforms)],path3d(shape))],caps=fullcaps);
module sweep(shape, transformations, closed=false, caps, convexity=10,
module sweep(shape, transforms, closed=false, caps, convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf = sweep(shape, transformations, closed, caps);
vnf = sweep(shape, transforms, closed, caps);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
@ -904,9 +917,9 @@ module sweep(shape, transformations, closed=false, caps, convexity=10,
// Usage:
// path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms])
// Description:
// Takes as input a 2d shape (specified as a point list) and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true` then
// it returns a list of transformations suitable as input to `sweep`.
// Takes as input a 2D polygon path or region, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true`
// then it returns a list of transformations suitable as input to `sweep`.
// .
// The sweep operation has an ambiguity: the shape can rotate around the axis defined by the path. Several options provide
// methods for controlling this rotation. You can choose from three different methods for selecting the rotation of your shape.
@ -951,8 +964,8 @@ module sweep(shape, transformations, closed=false, caps, convexity=10,
// If the model is closed then the twist must be a multiple of 360/symmetry. The twist is normally spread uniformly along your shape
// based on the path length. If you set `twist_by_length` to false then the twist will be uniform based on the point count of your path.
// Arguments:
// shape = a 2d path describing the shape to be swept
// path = 3d path giving the path to sweep over
// shape = A 2D polygon path or region describing the shape to be swept.
// path = 3D path giving the path to sweep over
// method = one of "incremental", "natural" or "manual". Default: "incremental"
// normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise.
// closed = path is a closed loop. Default: false
@ -1201,7 +1214,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
assert(is_path(shape,2), "shape must be a 2d path")
assert(is_path(shape,2) || is_region(shape), "shape must be a 2d path or region.")
assert(is_path(path), "input path is not a path")
assert(!closed || !approx(path[0],select(path,-1)), "Closed path includes start point at the end")
let(
@ -1285,8 +1298,118 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
all([for(i=idx(start)) approx(start[i],end[i])]),
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
)
transforms ? transform_list : sweep(shape, transform_list, closed=false, caps=fullcaps);
transforms ? transform_list : sweep(clockwise_polygon(shape), transform_list, closed=false, caps=fullcaps);
// Function&Module: path_sweep2d()
// Usage:
// path_sweep2d(shape, path, <closed>, <quality>)
// Description:
// Takes an input 2D polygon (the shape) and a 2d path and constructs a polyhedron by sweeping the shape along the path.
// When run as a module returns the polyhedron geometry. When run as a function returns a VNF.
// .
// Unlike path_sweep(), local self-intersections (creases in the output) are allowed and do not produce CGAL errors.
// This is accomplished by using offset() calculations, which are more expensive than simply copying the shape along
// the path, so if you do not have local self-intersections, use path_sweep() instead. Note that global self-intersections
// will still give rise to CGAL errors. You should be able to handle these by partitioning your model. The y axis of the
// shape is mapped to the z axis in the swept polyhedron.
// The quality parameter is passed to offset to determine the offset quality.
// Arguments:
// shape = a 2D polygon describing the shape to be swept
// path = a 2D path giving the path to sweep over
// closed = path is a closed loop. Default: false
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
// quality = quality of offset used in calculation. Default: 1
// convexity = convexity parameter for polyhedron (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// Example: Sine wave example with self-intersections at each peak. This would fail with path_sweep().
// sinewave = [for(i=[-30:10:360*2+30]) [i/40,3*sin(i)]];
// path_sweep2d(circle(r=3,$fn=15), sinewave);
// Example: The ends can look weird if they are in a place where self intersection occurs. This is a natural result of how offset behaves at ends of a path.
// coswave = [for(i=[0:10:360*1.5]) [i/40,3*cos(i)]];
// zrot(-20)
// path_sweep2d( circle(r=3,$fn=15), coswave);
// Example: This closed path example works ok as long as the hole in the center remains open.
// ellipse = yscale(3,p=circle(r=3,$fn=120));
// path_sweep2d(circle(r=2.5,$fn=32), reverse(ellipse), closed=true);
// Example: When the hole is closed a global intersection renders the model invalid. You can fix this by taking the union of the two (valid) halves.
// ellipse = yscale(3,p=circle(r=3,$fn=120));
// L = len(ellipse);
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.2),ceil(L*.8)),closed=false);
// path_sweep2d(circle(r=3.25, $fn=32), select(ellipse,floor(L*.7),ceil(L*.3)),closed=false);
function path_sweep2d(shape, path, closed=false, caps, quality=1) =
let(
caps = is_def(caps) ? caps
: closed ? false : true,
capsOK = is_bool(caps) || (is_list(caps) && len(caps)==2 && is_bool(caps[0]) && is_bool(caps[1])),
fullcaps = is_bool(caps) ? [caps,caps] : caps
)
assert(capsOK, "caps must be boolean or a list of two booleans")
assert(!closed || !caps, "Cannot make closed shape with caps")
let(
profile = ccw_polygon(shape),
flip = closed && polygon_is_clockwise(path) ? -1 : 1,
path = flip ? reverse(path) : path,
proflist= transpose(
[for(pt = profile)
let(
ofs = offset(path, delta=-flip*pt.x, return_faces=true,closed=closed, quality=quality),
map = subindex(_ofs_vmap(ofs,closed=closed),1)
)
select(path3d(ofs[0],pt.y),map)
]
)
)
_skin_core([
each proflist,
if (closed) proflist[0]
],caps=fullcaps);
module path_sweep2d(profile, path, closed=false, caps, quality=1, convexity=10,
anchor="origin", cp, spin=0, orient=UP, extent=false)
{
vnf = path_sweep2d(profile, path, closed, caps, quality);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
// Extract vertex mapping from offset face list. The output of this function
// is a list of pairs [i,j] where i is an index into the parent curve and j is
// an index into the offset curve. It would probably make sense to rewrite
// offset() to return this instead of the face list and have offset_sweep
// use this input to assemble the faces it needs.
function _ofs_vmap(ofs,closed=false) =
let( // Caclulate length of the first (parent) curve
firstlen = max(flatten(ofs[1]))+1-len(ofs[0])
)
[
for(entry=ofs[1]) _ofs_face_edge(entry,firstlen),
if (!closed) _ofs_face_edge(select(ofs[1],-1),firstlen,second=true)
];
// Extract first (default) or second edge that connects the parent curve to its offset. The first input
// face is a list of 3 or 4 vertices as indices into the two curves where the parent curve vertices are
// numbered from 0 to firstlen-1 and the offset from firstlen and up. The firstlen pararameter is used
// to determine which curve the vertices belong to and to remove the offset so that the return gives
// the index into each curve with a 0 base.
function _ofs_face_edge(face,firstlen,second=false) =
let(
itry = min_index(face),
i = select(face,itry-1)<firstlen ? itry-1:itry,
edge1 = select(face,[i,i-1]),
edge2 = select(face,i+1)<firstlen ? select(face,[i+1,i+2])
: select(face,[i,i+1])
)
(second ? edge2 : edge1)-[0,firstlen];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -31,6 +31,7 @@ include <coords.scad>
include <geometry.scad>
include <regions.scad>
include <strings.scad>
include <skin.scad>
include <vnf.scad>
include <common.scad>
include <debug.scad>

View file

@ -626,7 +626,6 @@ function is_letter(s) =
// Arguments:
// fmt = The formatting string, with placeholders to format the values into.
// vals = The list of values to format.
// use_nbsp = Pad fields with HTML entity `&nbsp;` instead of spaces.
// Example(NORENDER):
// str_format("The value of {} is {:.14f}.", ["pi", PI]); // Returns: "The value of pi is 3.14159265358979."
// str_format("The value {1:f} is known as {0}.", ["pi", PI]); // Returns: "The value 3.141593 is known as pi."
@ -634,7 +633,7 @@ function is_letter(s) =
// str_format("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // Returns: "foo 12000true"
// str_format("{:-10s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostamus27.440"
// str_format("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostam 27.440"
function str_format(fmt, vals, use_nbsp=false) =
function str_format(fmt, vals) =
let(
parts = str_split(fmt,"{")
) str_join([
@ -676,7 +675,7 @@ function str_format(fmt, vals, use_nbsp=false) =
typ=="G"? upcase(fmt_float(val,default(prec,6))) :
assert(false,str("Unknown format type: ",typ)),
padlen = max(0,wid-len(unpad)),
padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : use_nbsp? "&nbsp;" : " "]),
padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : " "]),
out = left? str(unpad, padfill) : str(padfill, unpad)
)
out, raw
@ -692,7 +691,6 @@ function str_format(fmt, vals, use_nbsp=false) =
// Arguments:
// fmt = The formatting string, with placeholders to format the values into.
// vals = The list of values to format.
// use_nbsp = Pad fields with HTML entity `&nbsp;` instead of spaces.
// Example(NORENDER):
// echofmt("The value of {} is {:.14f}.", ["pi", PI]); // ECHO: "The value of pi is 3.14159265358979."
// echofmt("The value {1:f} is known as {0}.", ["pi", PI]); // ECHO: "The value 3.141593 is known as pi."
@ -700,10 +698,10 @@ function str_format(fmt, vals, use_nbsp=false) =
// echofmt("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // ECHO: "foo 12000true"
// echofmt("{:-10s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostamus27.440"
// echofmt("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostam 27.440"
function echofmt(fmt, vals, use_nbsp=false) = echo(str_format(fmt,vals,use_nbsp));
module echofmt(fmt, vals, use_nbsp=false) {
function echofmt(fmt, vals) = echo(str_format(fmt,vals));
module echofmt(fmt, vals) {
no_children($children);
echo(str_format(fmt,vals,use_nbsp));
echo(str_format(fmt,vals));
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -64,16 +64,17 @@ function struct_remove(struct, keyword) =
// Function: struct_val()
// Usage:
// struct_val(struct,keyword)
// struct_val(struct, keyword, default)
// Description:
// Returns the value for the specified keyword in the structure, or undef if the keyword is not present
// Returns the value for the specified keyword in the structure, or default value if the keyword is not present
// Arguments:
// struct = input structure
// keyword = keyword whose value to return
function struct_val(struct,keyword) =
// default = default value to return if keyword is not present, defaults to undef
function struct_val(struct, keyword, default=undef) =
assert(is_def(keyword),"keyword is missing")
let(ind = search([keyword],struct)[0])
ind == [] ? undef : struct[ind][1];
ind == [] ? default : struct[ind][1];
// Function: struct_keys()
@ -96,7 +97,7 @@ function struct_keys(struct) =
// struct = input structure
// name = optional structure name to list at the top of the output. Default: ""
function struct_echo(struct,name="") =
let( keylist = [for(entry=struct) str("&nbsp;&nbsp;",entry[0],": ",entry[1],"\n")])
let( keylist = [for(entry=struct) str(" ",entry[0],": ",entry[1],"\n")])
echo(str("\nStructure ",name,"\n",str_join(keylist)))
undef;

View file

@ -836,6 +836,28 @@ module test_linear_solve(){
test_linear_solve();
module test_cumprod(){
assert_equal(cumprod([1,2,3,4]), [1,2,6,24]);
assert_equal(cumprod([4]), [4]);
assert_equal(cumprod([]),[]);
assert_equal(cumprod([[2,3],[4,5],[6,7]]), [[2,3],[8,15],[48,105]]);
assert_equal(cumprod([[5,6,7]]),[[5,6,7]]);
assert_equal(cumprod([
[[1,2],[3,4]],
[[-4,5],[6,4]],
[[9,-3],[4,3]]
]),
[
[[1,2],[3,4]],
[[8,13],[12,31]],
[[124,15],[232,57]]
]);
assert_equal(cumprod([[[1,2],[3,4]]]), [[[1,2],[3,4]]]);
}
test_cumprod();
module test_outer_product(){
assert_equal(outer_product([1,2,3],[4,5,6]), [[4,5,6],[8,10,12],[12,15,18]]);
assert_equal(outer_product([1,2],[4,5,6]), [[4,5,6],[8,10,12]]);
@ -867,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],

View file

@ -27,6 +27,9 @@ module test_struct_val() {
assert(struct_val(st,"Foo") == 91);
assert(struct_val(st,"Bar") == 28);
assert(struct_val(st,"Baz") == 9);
assert(struct_val(st,"Baz",5) == 9);
assert(struct_val(st,"Qux") == undef);
assert(struct_val(st,"Qux",5) == 5);
}
test_struct_val();

View file

@ -106,14 +106,23 @@ test_up();
module test_scale() {
cb = cube(1);
vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]];
for (val=vals) {
assert_equal(scale(point2d(val)), [[val.x,0,0],[0,val.y,0],[0,0,1]]);
assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]);
assert_equal(scale(val, p=[1,2,3]), vmul([1,2,3], val));
scale(val) nil();
}
assert_equal(scale(3), [[3,0,0,0],[0,3,0,0],[0,0,3,0],[0,0,0,1]]);
assert_equal(scale(3, p=[1,2,3]), 3*[1,2,3]);
assert_equal(scale(3, p=cb), cube(3));
assert_equal(scale(2, p=square(1)), square(2));
assert_equal(scale(2, cp=[1,1], p=square(1)), square(2, center=true));
assert_equal(scale([2,3], p=square(1)), square([2,3]));
assert_equal(scale([2,2], cp=[0.5,0.5], p=square(1)), move([-0.5,-0.5], p=square([2,2])));
assert_equal(scale([2,3,4], p=cb), cube([2,3,4]));
assert_equal(scale([-2,-3,-4], p=cb), [[for (p=cb[0]) vmul(p,[-2,-3,-4])], [for (f=cb[1]) reverse(f)]]);
// Verify that module at least doesn't crash.
scale(-5) scale(5) nil();
}

View file

@ -524,12 +524,12 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p);
// Function&Module: scale()
// Usage: As Module
// scale(SCALAR) ...
// scale([X,Y,Z]) ...
// scale(SCALAR, <cp>) ...
// scale([X,Y,Z], <cp>) ...
// Usage: Scale Points
// pts = scale(v, p);
// pts = scale(v, p, <cp>);
// Usage: Get Scaling Matrix
// mat = scale(v);
// mat = scale(v, <cp>);
// Description:
// Scales by the [X,Y,Z] scaling factors given in `v`. If `v` is given as a scalar number, all axes are scaled uniformly by that amount.
// * Called as the built-in module, scales all children.
@ -541,6 +541,7 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p);
// * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix.
// Arguments:
// v = Either a numeric uniform scaling factor, or a list of [X,Y,Z] scaling factors. Default: 1
// cp = If given, centers the scaling on the point `cp`.
// p = If called as a function, the point or list of points to scale.
// Example(NORENDER):
// pt1 = scale(3, p=[3,1,4]); // Returns: [9,3,12]
@ -552,20 +553,33 @@ function zrot(a=0, cp=undef, p=undef) = rot(a, cp=cp, p=p);
// path = circle(d=50,$fn=12);
// #stroke(path,closed=true);
// stroke(scale([1.5,3],p=path),closed=true);
function scale(v=1, p=undef) =
function scale(v=1, cp=[0,0,0], p=undef) =
assert(is_num(v) || is_vector(v))
assert(is_undef(p) || is_list(p))
let(v = is_num(v)? [v,v,v] : v)
let( v = is_num(v)? [v,v,v] : v )
is_undef(p)? (
len(v)==2? affine2d_scale(v) : affine3d_scale(point3d(v))
len(v)==2? (
cp==[0,0,0] || cp == [0,0] ? affine2d_scale(v) : (
affine2d_translate(point2d(cp)) *
affine2d_scale(v) *
affine2d_translate(point2d(-cp))
)
) : (
cp==[0,0,0] ? affine3d_scale(v) : (
affine3d_translate(point3d(cp)) *
affine3d_scale(v) *
affine3d_translate(point3d(-cp))
)
)
) : (
assert(is_list(p))
is_vector(p)? ( len(p)==2? vmul(p,point2d(v)) : vmul(p,point3d(v,1)) ) :
let( mat = scale(v=v, cp=cp) )
is_vector(p)? apply(mat, p) :
is_vnf(p)? let(inv=product([for (x=v) x<0? -1 : 1])) [
scale(v=v, p=p[0]),
apply(mat, p[0]),
inv>=0? p[1] : [for (l=p[1]) reverse(l)]
] :
[ for (pp=p) scale(v=v, p=pp) ]
apply(mat, p)
);
@ -591,7 +605,8 @@ function scale(v=1, p=undef) =
//
// Arguments:
// x = Factor to scale by, along the X axis.
// p = A point or path to scale, when called as a function.
// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[cp,0,0]`
// p = A point, path, bezier patch, or VNF to scale, when called as a function.
// planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix.
//
// Example: As Module
@ -601,9 +616,20 @@ function scale(v=1, p=undef) =
// path = circle(d=50,$fn=12);
// #stroke(path,closed=true);
// stroke(xscale(2,p=path),closed=true);
module xscale(x=1) scale([x,1,1]) children();
module xscale(x=1, cp=0) {
cp = is_num(cp)? [cp,0,0] : cp;
if (cp == [0,0,0]) {
scale([x,1,1]) children();
} else {
translate(cp) scale([x,1,1]) translate(-cp) children();
}
}
function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)==2))? scale([x,1],p=p) : scale([x,1,1],p=p);
function xscale(x=1, cp=0, p, planar=false) =
let( cp = is_num(cp)? [cp,0,0] : cp )
(planar || (!is_undef(p) && len(p)==2))
? scale([x,1], cp=cp, p=p)
: scale([x,1,1], cp=cp, p=p);
// Function&Module: yscale()
@ -627,7 +653,8 @@ function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)
//
// Arguments:
// y = Factor to scale by, along the Y axis.
// p = A point or path to scale, when called as a function.
// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[0,cp,0]`
// p = A point, path, bezier patch, or VNF to scale, when called as a function.
// planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix.
//
// Example: As Module
@ -637,9 +664,20 @@ function xscale(x=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)
// path = circle(d=50,$fn=12);
// #stroke(path,closed=true);
// stroke(yscale(2,p=path),closed=true);
module yscale(y=1) scale([1,y,1]) children();
module yscale(y=1, cp=0) {
cp = is_num(cp)? [0,cp,0] : cp;
if (cp == [0,0,0]) {
scale([1,y,1]) children();
} else {
translate(cp) scale([1,y,1]) translate(-cp) children();
}
}
function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)==2))? scale([1,y],p=p) : scale([1,y,1],p=p);
function yscale(y=1, cp=0, p, planar=false) =
let( cp = is_num(cp)? [0,cp,0] : cp )
(planar || (!is_undef(p) && len(p)==2))
? scale([1,y],p=p)
: scale([1,y,1],p=p);
// Function&Module: zscale()
@ -663,7 +701,8 @@ function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)
//
// Arguments:
// z = Factor to scale by, along the Z axis.
// p = A point or path to scale, when called as a function.
// cp = If given as a point, centers the scaling on the point `cp`. If given as a scalar, centers scaling on the point `[0,0,cp]`
// p = A point, path, bezier patch, or VNF to scale, when called as a function.
// planar = If true, and `p` is not given, then the matrix returned is an affine2d matrix instead of an affine3d matrix.
//
// Example: As Module
@ -671,11 +710,20 @@ function yscale(y=1, p=undef, planar=false) = (planar || (!is_undef(p) && len(p)
//
// Example: Scaling Points
// path = xrot(90,p=path3d(circle(d=50,$fn=12)));
// #trace_polyline(path);
// trace_polyline(zscale(2,p=path));
module zscale(z=1) scale([1,1,z]) children();
// #trace_path(path);
// trace_path(zscale(2,p=path));
module zscale(z=1, cp=0) {
cp = is_num(cp)? [0,0,cp] : cp;
if (cp == [0,0,0]) {
scale([1,1,z]) children();
} else {
translate(cp) scale([1,1,z]) translate(-cp) children();
}
}
function zscale(z=1, p=undef) = scale([1,1,z],p=p);
function zscale(z=1, cp=0, p) =
let( cp = is_num(cp)? [0,0,cp] : cp )
scale([1,1,z], cp=cp, p=p);
// Function&Module: mirror()
@ -917,7 +965,7 @@ function zflip(z=0,p) =
// color("blue") move_copies(pts) circle(d=3, $fn=8);
// Example(FlatSpin): Calling as a 3D Function
// pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3);
// trace_polyline(close_path(pts), showpts=true);
// trace_path(close_path(pts), showpts=true);
module skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0)
{
multmatrix(

View file

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,459];
BOSL_VERSION = [2,0,474];
// Section: BOSL Library Version Functions

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

View file

@ -72,7 +72,7 @@ function hex_offsets(n, d, lev=0, arr=[]) =
// Usage:
// wiring(path, wires, [wirediam], [rounding], [wirenum], [bezsteps]);
// Arguments:
// path = The 3D polyline path that the wire bundle should follow.
// path = The 3D path that the wire bundle should follow.
// wires = The number of wires in the wiring bundle.
// wirediam = The diameter of each wire in the bundle.
// rounding = The radius that the path corners will be rounded to.
@ -90,7 +90,7 @@ module wiring(path, wires, wirediam=2, rounding=10, wirenum=0, bezsteps=12) {
];
offsets = hex_offsets(wires, wirediam);
bezpath = fillet_path(path, rounding);
poly = simplify_path(path3d(bezier_polyline(bezpath, bezsteps)));
poly = simplify_path(path3d(bezier_path(bezpath, bezsteps)));
n = max(segs(wirediam), 8);
r = wirediam/2;
for (i = [0:1:wires-1]) {