From bb77faa0c9a9bb7a6fd28ac8e81d438d80930127 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 6 Oct 2021 21:16:39 -0400 Subject: [PATCH] assorted doc fixes eliminate trace_path _path_self_intersections fix --- attachments.scad | 8 +++--- beziers.scad | 49 ++++++++++++++++++++++++------------ distributors.scad | 2 +- drawing.scad | 54 +++------------------------------------ paths.scad | 63 ++++++++++++++++++++++++++-------------------- regions.scad | 64 +++++++++++++++++++++++++++++------------------ transforms.scad | 25 +++++++++++++----- vnf.scad | 12 ++++----- 8 files changed, 142 insertions(+), 135 deletions(-) diff --git a/attachments.scad b/attachments.scad index 09ae640..d75464e 100644 --- a/attachments.scad +++ b/attachments.scad @@ -194,16 +194,16 @@ module orient(dir, anchor, spin) { // See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile() // Description: // Attaches children to a parent object at an anchor point and orientation. Attached objects will -// be overlapped into the parent object by a little bit, as specified by the default `$overlap` -// value (0.01 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD -// from making non-manifold objects. You can also define `$overlap=` as an argument in a parent +// be overlapped into the parent object by a little bit, as specified by the `$overlap` +// value (0 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD +// from making non-manifold objects. You can define `$overlap=` as an argument in a parent // module to set the default for all attachments to it. For a more step-by-step explanation of // attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. // Arguments: // from = The vector, or name of the parent anchor point to attach to. // to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally. // --- -// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0.01` by default. +// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0` by default. // norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point. // Example: // spheroid(d=20) { diff --git a/beziers.scad b/beziers.scad index e8274b3..1d98302 100644 --- a/beziers.scad +++ b/beziers.scad @@ -573,9 +573,9 @@ function bezier_line_intersection(curve, line) = // p0 = [40, 0]; // p1 = [0, 0]; // p2 = [30, 30]; -// trace_path([p0,p1,p2], showpts=true, size=0.5, color="green"); +// stroke([p0,p1,p2], dots=true, color="green", dots_color="blue", width=0.5); // fbez = fillet3pts(p0,p1,p2, 10); -// trace_bezier(slice(fbez, 1, -2), size=1); +// trace_bezier(slice(fbez, 1, -2)); function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let( r = get_radius(r=r,d=d), v0 = unit(p0-p1), @@ -708,8 +708,7 @@ function bezier_path_length(path, N=3, max_deflect=0.001) = // [60,25], [70,0], [80,-25], // [80,-50], [50,-50] // ]; -// trace_path(bez, size=1, N=3, showpts=true); -// trace_path(bezier_path(bez, N=3), size=3); +// trace_bezier(bez, N=3, width=2); function bezier_path(bezier, splinesteps=16, N=3, endpoint=true) = assert(is_path(bezier)) assert(is_int(N)) @@ -822,8 +821,8 @@ function path_to_bezier(path, closed=false, tangents, uniform=false, size, relsi // Example(2D): // pline = [[40,0], [0,0], [35,35], [0,70], [-10,60], [-5,55], [0,60]]; // bez = fillet_path(pline, 10); -// trace_path(pline, showpts=true, size=0.5, color="green"); -// trace_bezier(bez, size=1); +// stroke(pline, dots=true, width=0.5, color="green", dots_color="blue"); +// trace_bezier(bez); function fillet_path(pts, fillet, maxerr=0.1) = concat( [pts[0], pts[0]], (len(pts) < 3)? [] : [ @@ -851,11 +850,11 @@ function fillet_path(pts, fillet, maxerr=0.1) = concat( // Example(2D): // bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]]; // closed = bezier_close_to_axis(bez); -// trace_bezier(closed, size=1); +// trace_bezier(closed); // Example(2D): // bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]]; // closed = bezier_close_to_axis(bez, axis="Y"); -// trace_bezier(closed, size=1); +// trace_bezier(closed); function bezier_close_to_axis(bezier, axis="X", N=3) = assert(is_path(bezier,2), "bezier_close_to_axis() can only work on 2D bezier paths.") assert(is_int(N)) @@ -892,11 +891,11 @@ function bezier_close_to_axis(bezier, axis="X", N=3) = // Example(2D): // bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]]; // closed = bezier_offset([0,-5], bez); -// trace_bezier(closed, size=1); +// trace_bezier(closed); // Example(2D): // bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]]; // closed = bezier_offset([-5,0], bez); -// trace_bezier(closed, size=1); +// trace_bezier(closed); function bezier_offset(offset, bezier, N=3) = assert(is_vector(offset,2)) assert(is_path(bezier,2), "bezier_offset() can only work on 2D bezier paths.") @@ -936,7 +935,7 @@ function bezier_offset(offset, bezier, N=3) = // [80,-50], [50,-50], [30,-50], // [5,-30], [0,0] // ]; -// trace_bezier(bez, N=3, size=3); +// trace_bezier(bez, N=3, width=3); // linear_extrude(height=0.1) bezier_polygon(bez, N=3); module bezier_polygon(bezier, splinesteps=16, N=3) { assert(is_path(bezier,2), "bezier_polygon() can only work on 2D bezier paths."); @@ -969,17 +968,35 @@ module bezier_polygon(bezier, splinesteps=16, N=3) { // [ 14, -5], [ 15, 0], [16, 5], // [ 5, 10], [ 0, 10] // ]; -// trace_bezier(bez, N=3, size=0.5); -module trace_bezier(bez, size=1, N=3) { +// trace_bezier(bez, N=3, width=0.5); +module trace_bezier(bez, width=1, N=3) { 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_path(bez, N=N, showpts=true, size=size, color="green"); - trace_path(bezier_path(bez, N=N), size=size, color="cyan"); + $fn=8; + stroke(bezier_path(bez, N=N), width=width, color="cyan"); + color("green") + if (N!=3) + stroke(path3d(path), width=size); + else + for(i=[1:3:len(bez)]) stroke(select(bez,max(0,i-2), min(len(bez)-1,i)), width=width); + twodim = len(bez[0])==2; + move_copies(bez) + if ($idx % N ==0) + color("blue") if (twodim) circle(d=width*2.5); else sphere(d=width*2.5); + else + color("red") + if (twodim){ + rect([width/2, width*3],center=true); + rect([width*3, width/2],center=true); + } else { + zcyl(d=width/2, h=width*3); + xcyl(d=width/2, h=width*3); + ycyl(d=width/2, h=width*3); + } } - // Section: Patch Functions diff --git a/distributors.scad b/distributors.scad index 11058cb..eec05b4 100644 --- a/distributors.scad +++ b/distributors.scad @@ -109,7 +109,7 @@ module move_copies(a=[[0,0,0]]) // cube(size=[1,3,1],center=true); // cube(size=[3,1,1],center=true); // } -// Example(2D): +// Example(2D): The functional form of line_of() returns a list of points. // pts = line_of([10,5],n=5); // move_copies(pts) circle(d=2); module line_of(spacing, n, l, p1, p2) diff --git a/drawing.scad b/drawing.scad index a619e7b..72bf323 100644 --- a/drawing.scad +++ b/drawing.scad @@ -561,54 +561,6 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { -// Module: trace_path() -// Usage: -// trace_path(path, [closed=], [showpts=], [N=], [size=], [color=]); -// Description: -// Renders lines between each point of a path. -// Can also optionally show the individual vertex points. -// Arguments: -// 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,VPD=44.4): -// 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); - path = closed? close_path(path) : path; - if (showpts) { - for (i = [0:1:len(path)-1]) { - translate(path[i]) { - if (i % N == 0) { - color("blue") sphere(d=size*2.5, $fn=8); - } else { - color("red") { - cylinder(d=size/2, h=size*3, center=true, $fn=8); - xrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); - yrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); - } - } - } - } - } - if (N!=3) { - color(color) stroke(path3d(path), width=size, $fn=8); - } else { - for (i = [0:1:len(path)-2]) { - if (N != 3 || (i % N) != 1) { - color(color) extrude_from_to(path[i], path[i+1]) circle(d=size, $fn=sides); - } - } - } -} - - // Section: Computing paths // Function&Module: arc() @@ -661,7 +613,7 @@ module trace_path(path, closed=false, showpts=false, N=1, size=1, color="yellow" // stroke(closed=true, path); // Example(FlatSpin,VPD=175): // path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]); -// trace_path(path, showpts=true, color="cyan"); +// stroke(path, dots=true, dots_color="blue"); function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) = assert(is_bool(endpoint)) !endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true") @@ -774,9 +726,9 @@ module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false) // d1 = Diameter of bottom of helix // d2 = Diameter of top of helix // Example(3D): -// trace_path(helix(turns=2.5, h=100, r=50), N=1, showpts=true); +// stroke(helix(turns=2.5, h=100, r=50), dots=true, dots_color="blue"); // Example(3D): Helix that turns the other way -// trace_path(helix(turns=-2.5, h=100, r=50), N=1, showpts=true); +// stroke(helix(turns=-2.5, h=100, r=50), dots=true, dots_color="blue"); // Example(3D): Flat helix (note points are still 3d) // stroke(helix(h=0,r1=50,r2=25,l=0, turns=4)); function helix(l,h,turns,angle, r, r1, r2, d, d1, d2)= diff --git a/paths.scad b/paths.scad index db2c279..17123a3 100644 --- a/paths.scad +++ b/paths.scad @@ -168,6 +168,9 @@ function path_segment_lengths(path, closed=false) = // point is zero and the final point is 1. If the path is closed the length of the output // will have one extra point because of the final connecting segment that connects the last // point of the path to the first point. +// Arguments: +// path = path to operate on +// closed = set to true if path is closed. Default: false function path_length_fractions(path, closed=false) = assert(is_path(path)) assert(is_bool(closed)) @@ -208,33 +211,39 @@ function path_length_fractions(path, closed=false) = /// // isects == [[[-33.3333, 0], 0, 0.666667, 4, 0.333333], [[33.3333, 0], 1, 0.333333, 3, 0.666667]] /// stroke(path, closed=true, width=1); /// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10); -function _path_self_intersections(path, closed=true, eps=EPSILON) = +function _path_self_intersections5(path, closed=true, eps=EPSILON) = let( path = closed ? close_path(path,eps=eps) : path, plen = len(path) ) [ for (i = [0:1:plen-3]) let( a1 = path[i], - a2 = path[i+1], - // The sign of signals is positive if the segment is one one side of - // the line defined by [a1,a2] and negative on the other side. - seg_normal = unit([-(a2-a1).y, (a2-a1).x]), - signals = [for(j=[i+2:1:plen-(i==0 && closed? 2: 1)]) path[j]-a1 ]*seg_normal + a2 = path[i+1], + seg_normal = unit([-(a2-a1).y, (a2-a1).x],[0,0]), + vals = path*seg_normal, + ref = a1*seg_normal, + // The value of vals[j]-ref is positive if vertex j is one one side of the + // line [a1,a2] and negative on the other side. Only a segment with opposite + // signs at its two vertices can have an intersection with segment + // [a1,a2]. The variable signals is zero when abs(vals[j]-ref) is less than + // eps and the sign of vals[j]-ref otherwise. + signals = [for(j=[i+2:1:plen-(i==0 && closed? 2: 1)]) vals[j]-ref > eps ? 1 + : vals[j]-ref < -eps ? -1 + : 0] ) + if(max(signals)>=0 && min(signals)<=0 ) // some remaining edge intersects line [a1,a2] for(j=[i+2:1:plen-(i==0 && closed? 3: 2)]) - // The signals test requires the two signals to have different signs, - // otherwise b1 and b2 are on the same side of the line defined by [a1,a2] - // and hence intersection is impossible - if( signals[j-i-2]*signals[j-i-1] <= 0 ) - let( - b1 = path[j], - b2 = path[j+1] - ) - // This test checks that a1 and a2 are on opposite sides of the - // line defined by [b1,b2]. - if( cross(b2-b1, a1-b1)*cross(b2-b1, a2-b1) <= 0 ) - let(isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)) - if (isect) [isect[0], i, isect[1], j, isect[2]] + if( signals[j-i-2]*signals[j-i-1]<=0 ) let( // segm [b1,b2] intersects line [a1,a2] + b1 = path[j], + b2 = path[j+1], + isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps) + ) + if (isect + && isect[1]> (i==0 && !closed? -eps: 0) + && isect[1]<= 1+eps + && isect[2]> 0 + && isect[2]<= 1+eps) + [isect[0], i, isect[1], j, isect[2]] ]; @@ -368,7 +377,7 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length // maxlen = The maximum allowed path segment length. // --- // closed = If true, treat path like a closed polygon. Default: true -// Example: +// Example(2D): // path = pentagon(d=100); // spath = subdivide_long_segments(path, 10, closed=true); // stroke(path); @@ -487,14 +496,14 @@ function path_closest_point(path, pt) = // path = path to find the tagent vectors for // closed = set to true of the path is closed. Default: false // uniform = set to false to correct for non-uniform sampling. Default: true -// Example(3D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable +// Example(3D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle. // rect = square([10,3]); // tangents = path_tangents(rect,closed=true); // stroke(rect,closed=true, width=0.1); // color("purple") // for(i=[0:len(tangents)-1]) // stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); -// Example(3D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable +// Example(3D): Setting uniform to false corrects the distorted derivatives for this example: // rect = square([10,3]); // tangents = path_tangents(rect,closed=true,uniform=false); // stroke(rect,closed=true, width=0.1); @@ -1020,7 +1029,7 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // left(100)region(outside); // rainbow(outside) // stroke($item,closed=true); -// Example: +// Example(2D): // N=12; // ang=360/N; // sr=10; @@ -1033,19 +1042,19 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // "move", sr]); // stroke(path, width=.3); // right(20)rainbow(polygon_parts(path)) polygon($item); -// Example: overlapping path segments disappear +// Example(2D): overlapping path segments disappear // path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]]; // stroke(path,width=0.3); // right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true); -// Example: Path segments disappear outside as well +// Example(2D): Path segments disappear outside as well // path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]); // back(2)stroke(path,width=.3); // fwd(12)rainbow(polygon_parts(path)) polygon($item); -// Example: This shape has six components +// Example(2D): This shape has six components // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]); // polygon(path); // right(22)rainbow(polygon_parts(path)) polygon($item); -// Example: when the loops of the shape overlap then nonzero gives a different result than the even-odd method. +// Example(2D): when the loops of the shape overlap then nonzero gives a different result than the even-odd method. // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]); // polygon(path); // right(27)rainbow(polygon_parts(path)) polygon($item); diff --git a/regions.scad b/regions.scad index 22aafd5..56e4faf 100644 --- a/regions.scad +++ b/regions.scad @@ -113,7 +113,7 @@ module region(r) // Function: point_in_region() // Usage: -// check = point_in_region(point, region); +// check = point_in_region(point, region, [eps]); // Description: // Tests if a point is inside, outside, or on the border of a region. // Returns -1 if the point is outside the region. @@ -256,14 +256,26 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON) [for(s=subpaths) if (len(s)>1) s]; -// Function: split_nested_region() +// Function: region_parts() // Usage: -// rgns = split_nested_region(region); +// rgns = region_parts(region); // Description: -// Separates the distinct (possibly nested) positive subregions of a larger compound region. -// Returns a list of regions, such that each returned region has exactly one positive outline -// and zero or more void outlines. -function split_nested_region(region) = +// Divides a region into a list of connected regions. Each connected region has exactly one outside boundary +// and zero or more outlines defining internal holes. Note that behavior is undefined on invalid regions whose +// components intersect each other. +// Example(2D,NoAxes): +// R = [for(i=[1:7]) square(i,center=true)]; +// region_list = split_nested_region(R); +// rainbow(region_list) region($item); +// Example(2D,NoAxes): +// R = [back(7,square(3,center=true)), +// square([20,10],center=true), +// left(5,square(8,center=true)), +// for(i=[4:2:8]) +// right(5,square(i,center=true))]; +// region_list = split_nested_region(R); +// rainbow(region_list) region($item); +function region_parts(region) = let( paths = sort(idx=0, [ for(i = idx(region)) let( @@ -360,7 +372,7 @@ function _cleave_connected_region(region) = // vnf = If given, the faces are added to this VNF. Default: `EMPTY_VNF` function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) = let ( - regions = split_nested_region(region), + regions = region_parts(region), vnfs = [ if (vnf != EMPTY_VNF) vnf, for (rgn = regions) let( @@ -434,12 +446,13 @@ module linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg, } -function linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg, style="default", anchor_isect=false, anchor, spin=0, orient=UP) = +function linear_sweep(region, height=1, center, twist=0, scale=1, slices, + maxseg, style="default", anchor_isect=false, anchor, spin=0, orient=UP) = let( anchor = get_anchor(anchor,center,BOT,BOT), region = is_path(region)? [region] : region, cp = mean(pointlist_bounds(flatten(region))), - regions = split_nested_region(region), + regions = region_parts(region), slices = default(slices, floor(twist/5+1)), step = twist/slices, hstep = height/slices, @@ -939,7 +952,8 @@ function union(regions=[],b=undef,c=undef,eps=EPSILON) = // color("green") region(difference(shape1,shape2)); function difference(regions=[],b=undef,c=undef,eps=EPSILON) = b!=undef? difference(concat([regions],[b],c==undef?[]:[c]), eps=eps) : - len(regions)<=1? regions[0] : + len(regions)==0? [] : + len(regions)==1? regions[0] : difference( let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)]) concat( @@ -968,16 +982,17 @@ function difference(regions=[],b=undef,c=undef,eps=EPSILON) = // for (shape = [shape1,shape2]) color("red") stroke(shape, width=0.5, closed=true); // color("green") region(intersection(shape1,shape2)); function intersection(regions=[],b=undef,c=undef,eps=EPSILON) = - b!=undef? intersection(concat([regions],[b],c==undef?[]:[c]),eps=eps) : - len(regions)<=1? regions[0] : - intersection( - let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)]) - concat( - [_tagged_region(regions[0],regions[1],["I","S"],["I"],eps=eps)], - [for (i=[2:1:len(regions)-1]) regions[i]] - ), - eps=eps - ); + b!=undef? intersection(concat([regions],[b],c==undef?[]:[c]),eps=eps) + : len(regions)==0 ? [] + : len(regions)==1? regions[0] + : let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)]) + intersection([ + _tagged_region(regions[0],regions[1],["I","S"],["I"],eps=eps), + for (i=[2:1:len(regions)-1]) regions[i] + ], + eps=eps + ); + // Function&Module: exclusive_or() @@ -989,16 +1004,17 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) = // Description: // When called as a function and given a list of regions, where each region is a list of closed // 2D paths, returns the boolean exclusive_or of all given regions. Result is a single region. -// When called as a module, performs a boolean exclusive-or of up to 10 children. +// When called as a module, performs a boolean exclusive-or of up to 10 children. Note that the +// xor operator tends to produce shapes that meet at corners, which do not render in CGAL. // Arguments: // regions = List of regions to exclusive_or. Each region is a list of closed paths. -// Example(2D): As Function +// Example(2D): As Function. A linear_sweep of this shape fails to render in CGAL. // shape1 = move([-8,-8,0], p=circle(d=50)); // shape2 = move([ 8, 8,0], p=circle(d=50)); // for (shape = [shape1,shape2]) // color("red") stroke(shape, width=0.5, closed=true); // color("green") region(exclusive_or(shape1,shape2)); -// Example(2D): As Module +// Example(2D): As Module. A linear_extrude() of the resulting geometry fails to render in CGAL. // exclusive_or() { // square(40,center=false); // circle(d=40); diff --git a/transforms.scad b/transforms.scad index 6a68d94..bf9030a 100644 --- a/transforms.scad +++ b/transforms.scad @@ -9,6 +9,10 @@ // of the shortcuts can return a matrix representing the operation // the shortcut performs. The rotation and scaling shortcuts accept // an optional centerpoint for the rotation or scaling operation. +// . +// Almost all of the transformation functions take a point, a point +// list, bezier patch, or VNF as a second positional argument to +// operate on. The exceptions are rot(), frame_map() and skew(). // Includes: // include ////////////////////////////////////////////////////////////////////// @@ -378,6 +382,7 @@ function up(z=0, p) = move([0,0,z],p=p); // * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF. // * Called as a function without a `p` argument, and `planar` is true, returns the affine2d rotational matrix. The angle `a` must be a scalar. // * Called as a function without a `p` argument, and `planar` is false, returns the affine3d rotational matrix. +// Note that unlike almost all the other transformations, the `p` argument must be given as a named argument. // // Arguments: // a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees. If `planar` is true or if `p` holds 2d data, or if you use the `from` and `to` arguments then `a` must be a scalar. Default: `0` @@ -824,8 +829,8 @@ function yscale(y=1, p, cp=0, planar=false) = // // Example: Scaling Points // path = xrot(90,p=path3d(circle(d=50,$fn=12))); -// #trace_path(path); -// trace_path(zscale(2,p=path)); +// #stroke(path,closed=true); +// stroke(zscale(2,path),closed=true); module zscale(z=1, p, cp=0) { assert(is_undef(p), "Module form `zscale()` does not accept p= argument."); cp = is_num(cp)? [0,0,cp] : cp; @@ -1119,15 +1124,23 @@ function zflip(p, z=0) = // x = Destination 3D vector for x axis. // y = Destination 3D vector for y axis. // z = Destination 3D vector for z axis. +// p = If given, the point, path, patch, or VNF to operate on. Function use only. // reverse = reverse direction of the map for orthogonal inputs. Default: false // Example: Remap axes after linear extrusion // frame_map(x=[0,1,0], y=[0,0,1]) linear_extrude(height=10) square(3); // Example: This map is just a rotation around the z axis -// mat = frame_map(x=[1,1,0], y=[-1,1,0]); +// mat = frame_map(x=[1,1,0], y=[-1,1,0]); +// multmatrix(mat) frame_ref(); // Example: This map is not a rotation because x and y aren't orthogonal -// mat = frame_map(x=[1,0,0], y=[1,1,0]); -// Example: This sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1] +// frame_map(x=[1,0,0], y=[1,1,0]) cube(10); +// Example: This sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]. (Original directions shown in light shade, final directions shown dark.) // mat = frame_map(x=[0,1,1], y=[0,-1,1]) * frame_map(x=[1,1,0], y=[-1,1,0],reverse=true); +// color("purple",alpha=.2) stroke([[0,0,0],10*[1,1,0]]); +// color("green",alpha=.2) stroke([[0,0,0],10*[-1,1,0]]); +// multmatrix(mat) { +// color("purple") stroke([[0,0,0],10*[1,1,0]]); +// color("green") stroke([[0,0,0],10*[-1,1,0]]); +// } function frame_map(x,y,z, p, reverse=false) = is_def(p) ? apply(frame_map(x,y,z,reverse=reverse), p) @@ -1223,7 +1236,7 @@ module frame_map(x,y,z,p,reverse=false) // color("blue") move_copies(pts) circle(d=3, $fn=8); // Example(FlatSpin,VPD=175): Calling as a 3D Function // pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3); -// trace_path(close_path(pts), showpts=true); +// stroke(pts,closed=true,dots=true,dots_color="blue"); module skew(p, sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0) { assert(is_undef(p), "Module form `skew()` does not accept p= argument.") diff --git a/vnf.scad b/vnf.scad index 3d90002..1534f5c 100644 --- a/vnf.scad +++ b/vnf.scad @@ -202,16 +202,17 @@ function vnf_vertex_array( // Description: // Produces a vnf from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables // the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true. +// You cannot wrap columns: if you need to do that you'll need to combine two VNF arrays that share edges. // Arguments: // points = List of point lists for each row // row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length. // reverse = Set this to reverse the direction of the faces -// Example(3D): Each row has one more point than the preceeding one. +// Example(3D,NoAxes): Each row has one more point than the preceeding one. // pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]]; // vnf = vnf_tri_array(pts); // vnf_wireframe(vnf,width=0.1); // color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9); -// Example(3D): Each row has one more point than the preceeding one. +// Example(3D,NoAxes): Each row has two more points than the preceeding one. // pts = [for(y=[0:2:10]) [for(x=[-y/2:y/2]) [x,y,y]]]; // vnf = vnf_tri_array(pts); // vnf_wireframe(vnf,width=0.1); @@ -230,7 +231,7 @@ function vnf_vertex_array( // vnf=vnf_tri_array(pts2)); // color("green")vnf_wireframe(vnf,width=0.1); // vnf_polyhedron(vnf); -// Example(3D): Point count can change irregularly +// Example(3D,NoAxes): Point count can change irregularly // lens = [10,9,7,5,6,8,8,10]; // pts = [for(y=idx(lens)) lerpn([-lens[y],y,y],[lens[y],y,y],lens[y])]; // vnf = vnf_tri_array(pts); @@ -283,9 +284,8 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) = // Description: // Given a list of VNF structures, merges them all into a single VNF structure. // When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`, -// drops unreferenced vertices and any final face with less than 3 vertices. -// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex -// are not dropped. +// and eliminates any faces with fewer than 3 vertices. +// (Unreferenced vertices of the input VNFs are not dropped.) // Arguments: // vnfs - a list of the VNFs to merge in one VNF. // cleanup - when true, consolidates the duplicate vertices of the merge. Default: false