mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-01 09:49:45 +00:00
Merge branch 'master' of https://github.com/revarbat/BOSL2
This commit is contained in:
commit
cd7a0c31a2
30 changed files with 896 additions and 281 deletions
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
12
arrays.scad
12
arrays.scad
|
@ -89,6 +89,18 @@ function select(list, start, end=undef) =
|
|||
: concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
|
||||
|
||||
|
||||
// Function: last()
|
||||
// Description:
|
||||
// Returns the last element of a list, or undef if empty.
|
||||
// Usage:
|
||||
// last(list)
|
||||
// Arguments:
|
||||
// list = The list to get the last element of.
|
||||
// Example:
|
||||
// l = [3,4,5,6,7,8,9];
|
||||
// last(l); // Returns 9.
|
||||
function last(list) = list[len(list)-1];
|
||||
|
||||
// Function: slice()
|
||||
// Description:
|
||||
// Returns a slice of a list. The first item is index 0.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
48
beziers.scad
48
beziers.scad
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
|
89
common.scad
89
common.scad
|
@ -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>);
|
||||
|
|
70
debug.scad
70
debug.scad
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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]]);
|
||||
|
|
38
math.scad
38
math.scad
|
@ -617,6 +617,44 @@ function _product(v, i=0, _tot) =
|
|||
|
||||
|
||||
|
||||
// Function: cumprod()
|
||||
// Description:
|
||||
// Returns a list where each item is the cumulative product of all items up to and including the corresponding entry in the input list.
|
||||
// If passed an array of vectors, returns a list of elementwise vector products. If passed a list of square matrices returns matrix
|
||||
// products multiplying in the order items appear in the list.
|
||||
// Arguments:
|
||||
// list = The list to get the product of.
|
||||
// Example:
|
||||
// cumprod([1,3,5]); // returns [1,3,15]
|
||||
// cumprod([2,2,2]); // returns [2,4,8]
|
||||
// cumprod([[1,2,3], [3,4,5], [5,6,7]])); // returns [[1, 2, 3], [3, 8, 15], [15, 48, 105]]
|
||||
function cumprod(list) =
|
||||
is_vector(list) ? _cumprod(list) :
|
||||
assert(is_consistent(list), "Input must be a consistent list of scalars, vectors or square matrices")
|
||||
is_matrix(list[0]) ? assert(len(list[0])==len(list[0][0]), "Matrices must be square") _cumprod(list)
|
||||
: _cumprod_vec(list);
|
||||
|
||||
function _cumprod(v,_i=0,_acc=[]) =
|
||||
_i==len(v) ? _acc :
|
||||
_cumprod(
|
||||
v, _i+1,
|
||||
concat(
|
||||
_acc,
|
||||
[_i==0 ? v[_i] : _acc[len(_acc)-1]*v[_i]]
|
||||
)
|
||||
);
|
||||
|
||||
function _cumprod_vec(v,_i=0,_acc=[]) =
|
||||
_i==len(v) ? _acc :
|
||||
_cumprod_vec(
|
||||
v, _i+1,
|
||||
concat(
|
||||
_acc,
|
||||
[_i==0 ? v[_i] : vmul(_acc[len(_acc)-1],v[_i])]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// Function: outer_product()
|
||||
// Usage:
|
||||
// x = outer_product(u,v);
|
||||
|
|
110
mutators.scad
110
mutators.scad
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
102
paths.scad
102
paths.scad
|
@ -1,6 +1,6 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: paths.scad
|
||||
// Polylines, polygons and paths.
|
||||
// Support for polygons and paths.
|
||||
// To use, add the following lines to the beginning of your file:
|
||||
// ```
|
||||
// include <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
regions.scad
12
regions.scad
|
@ -154,9 +154,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
|
|||
|
||||
// Function: split_path_at_region_crossings()
|
||||
// Usage:
|
||||
// polylines = split_path_at_region_crossings(path, region, [eps]);
|
||||
// paths = split_path_at_region_crossings(path, region, [eps]);
|
||||
// Description:
|
||||
// Splits a path into polyline sections wherever the path crosses the perimeter of a region.
|
||||
// Splits a path into sub-paths wherever the path crosses the perimeter of a region.
|
||||
// Splits may occur mid-segment, so new vertices will be created at the intersection points.
|
||||
// Arguments:
|
||||
// path = The path to split up.
|
||||
|
@ -166,9 +166,9 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
|
|||
// Example(2D):
|
||||
// path = square(50,center=false);
|
||||
// region = [circle(d=80), circle(d=40)];
|
||||
// polylines = split_path_at_region_crossings(path, region);
|
||||
// paths = split_path_at_region_crossings(path, region);
|
||||
// color("#aaa") region(region);
|
||||
// rainbow(polylines) stroke($item, closed=false, width=2);
|
||||
// rainbow(paths) stroke($item, closed=false, width=2);
|
||||
function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON) =
|
||||
let(
|
||||
path = deduplicate(path, eps=eps),
|
||||
|
@ -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]]
|
||||
],
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
80
shapes.scad
80
shapes.scad
|
@ -78,14 +78,15 @@ module cuboid(
|
|||
cnt = sum(e);
|
||||
r = first_defined([chamfer, rounding, 0]);
|
||||
c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)];
|
||||
c2 = vmul(corner,c/2);
|
||||
$fn = is_finite(chamfer)? 4 : segs(r);
|
||||
translate(vmul(corner,size/2-c)) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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
181
skin.scad
|
@ -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
|
||||
|
|
1
std.scad
1
std.scad
|
@ -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>
|
||||
|
|
12
strings.scad
12
strings.scad
|
@ -626,7 +626,6 @@ function is_letter(s) =
|
|||
// Arguments:
|
||||
// fmt = The formatting string, with placeholders to format the values into.
|
||||
// vals = The list of values to format.
|
||||
// use_nbsp = Pad fields with HTML entity ` ` instead of spaces.
|
||||
// Example(NORENDER):
|
||||
// str_format("The value of {} is {:.14f}.", ["pi", PI]); // Returns: "The value of pi is 3.14159265358979."
|
||||
// str_format("The value {1:f} is known as {0}.", ["pi", PI]); // Returns: "The value 3.141593 is known as pi."
|
||||
|
@ -634,7 +633,7 @@ function is_letter(s) =
|
|||
// str_format("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // Returns: "foo 12000true"
|
||||
// str_format("{:-10s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostamus27.440"
|
||||
// str_format("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostam 27.440"
|
||||
function str_format(fmt, vals, use_nbsp=false) =
|
||||
function str_format(fmt, vals) =
|
||||
let(
|
||||
parts = str_split(fmt,"{")
|
||||
) str_join([
|
||||
|
@ -676,7 +675,7 @@ function str_format(fmt, vals, use_nbsp=false) =
|
|||
typ=="G"? upcase(fmt_float(val,default(prec,6))) :
|
||||
assert(false,str("Unknown format type: ",typ)),
|
||||
padlen = max(0,wid-len(unpad)),
|
||||
padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : use_nbsp? " " : " "]),
|
||||
padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : " "]),
|
||||
out = left? str(unpad, padfill) : str(padfill, unpad)
|
||||
)
|
||||
out, raw
|
||||
|
@ -692,7 +691,6 @@ function str_format(fmt, vals, use_nbsp=false) =
|
|||
// Arguments:
|
||||
// fmt = The formatting string, with placeholders to format the values into.
|
||||
// vals = The list of values to format.
|
||||
// use_nbsp = Pad fields with HTML entity ` ` instead of spaces.
|
||||
// Example(NORENDER):
|
||||
// echofmt("The value of {} is {:.14f}.", ["pi", PI]); // ECHO: "The value of pi is 3.14159265358979."
|
||||
// echofmt("The value {1:f} is known as {0}.", ["pi", PI]); // ECHO: "The value 3.141593 is known as pi."
|
||||
|
@ -700,10 +698,10 @@ function str_format(fmt, vals, use_nbsp=false) =
|
|||
// echofmt("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // ECHO: "foo 12000true"
|
||||
// echofmt("{:-10s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostamus27.440"
|
||||
// echofmt("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostam 27.440"
|
||||
function echofmt(fmt, vals, use_nbsp=false) = echo(str_format(fmt,vals,use_nbsp));
|
||||
module echofmt(fmt, vals, use_nbsp=false) {
|
||||
function echofmt(fmt, vals) = echo(str_format(fmt,vals));
|
||||
module echofmt(fmt, vals) {
|
||||
no_children($children);
|
||||
echo(str_format(fmt,vals,use_nbsp));
|
||||
echo(str_format(fmt,vals));
|
||||
}
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||
|
|
11
structs.scad
11
structs.scad
|
@ -64,16 +64,17 @@ function struct_remove(struct, keyword) =
|
|||
|
||||
// Function: struct_val()
|
||||
// Usage:
|
||||
// struct_val(struct,keyword)
|
||||
// struct_val(struct, keyword, default)
|
||||
// Description:
|
||||
// Returns the value for the specified keyword in the structure, or undef if the keyword is not present
|
||||
// Returns the value for the specified keyword in the structure, or default value if the keyword is not present
|
||||
// Arguments:
|
||||
// struct = input structure
|
||||
// keyword = keyword whose value to return
|
||||
function struct_val(struct,keyword) =
|
||||
// default = default value to return if keyword is not present, defaults to undef
|
||||
function struct_val(struct, keyword, default=undef) =
|
||||
assert(is_def(keyword),"keyword is missing")
|
||||
let(ind = search([keyword],struct)[0])
|
||||
ind == [] ? undef : struct[ind][1];
|
||||
ind == [] ? default : struct[ind][1];
|
||||
|
||||
|
||||
// Function: struct_keys()
|
||||
|
@ -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(" ",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;
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,459];
|
||||
BOSL_VERSION = [2,0,474];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
139
vnf.scad
139
vnf.scad
|
@ -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
|
||||
|
|
|
@ -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]) {
|
||||
|
|
Loading…
Reference in a new issue