assorted doc fixes

eliminate trace_path
_path_self_intersections fix
This commit is contained in:
Adrian Mariano 2021-10-06 21:16:39 -04:00
parent 1147e181c5
commit bb77faa0c9
8 changed files with 142 additions and 135 deletions

View file

@ -194,16 +194,16 @@ module orient(dir, anchor, spin) {
// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile() // See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
// Description: // Description:
// Attaches children to a parent object at an anchor point and orientation. Attached objects will // 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` // be overlapped into the parent object by a little bit, as specified by the `$overlap`
// value (0.01 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD // value (0 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 // 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 // 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]]. // attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments: // Arguments:
// from = The vector, or name of the parent anchor point to attach to. // 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. // 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. // norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
// Example: // Example:
// spheroid(d=20) { // spheroid(d=20) {

View file

@ -573,9 +573,9 @@ function bezier_line_intersection(curve, line) =
// p0 = [40, 0]; // p0 = [40, 0];
// p1 = [0, 0]; // p1 = [0, 0];
// p2 = [30, 30]; // 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); // 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( function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let(
r = get_radius(r=r,d=d), r = get_radius(r=r,d=d),
v0 = unit(p0-p1), 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], // [60,25], [70,0], [80,-25],
// [80,-50], [50,-50] // [80,-50], [50,-50]
// ]; // ];
// trace_path(bez, size=1, N=3, showpts=true); // trace_bezier(bez, N=3, width=2);
// trace_path(bezier_path(bez, N=3), size=3);
function bezier_path(bezier, splinesteps=16, N=3, endpoint=true) = function bezier_path(bezier, splinesteps=16, N=3, endpoint=true) =
assert(is_path(bezier)) assert(is_path(bezier))
assert(is_int(N)) assert(is_int(N))
@ -822,8 +821,8 @@ function path_to_bezier(path, closed=false, tangents, uniform=false, size, relsi
// Example(2D): // Example(2D):
// pline = [[40,0], [0,0], [35,35], [0,70], [-10,60], [-5,55], [0,60]]; // pline = [[40,0], [0,0], [35,35], [0,70], [-10,60], [-5,55], [0,60]];
// bez = fillet_path(pline, 10); // bez = fillet_path(pline, 10);
// trace_path(pline, showpts=true, size=0.5, color="green"); // stroke(pline, dots=true, width=0.5, color="green", dots_color="blue");
// trace_bezier(bez, size=1); // trace_bezier(bez);
function fillet_path(pts, fillet, maxerr=0.1) = concat( function fillet_path(pts, fillet, maxerr=0.1) = concat(
[pts[0], pts[0]], [pts[0], pts[0]],
(len(pts) < 3)? [] : [ (len(pts) < 3)? [] : [
@ -851,11 +850,11 @@ function fillet_path(pts, fillet, maxerr=0.1) = concat(
// Example(2D): // Example(2D):
// bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]]; // bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]];
// closed = bezier_close_to_axis(bez); // closed = bezier_close_to_axis(bez);
// trace_bezier(closed, size=1); // trace_bezier(closed);
// Example(2D): // Example(2D):
// bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]]; // bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]];
// closed = bezier_close_to_axis(bez, axis="Y"); // 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) = 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_path(bezier,2), "bezier_close_to_axis() can only work on 2D bezier paths.")
assert(is_int(N)) assert(is_int(N))
@ -892,11 +891,11 @@ function bezier_close_to_axis(bezier, axis="X", N=3) =
// Example(2D): // Example(2D):
// bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]]; // bez = [[50,30], [40,10], [10,50], [0,30], [-10, 10], [-30,10], [-50,20]];
// closed = bezier_offset([0,-5], bez); // closed = bezier_offset([0,-5], bez);
// trace_bezier(closed, size=1); // trace_bezier(closed);
// Example(2D): // Example(2D):
// bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]]; // bez = [[30,50], [10,40], [50,10], [30,0], [10, -10], [10,-30], [20,-50]];
// closed = bezier_offset([-5,0], bez); // closed = bezier_offset([-5,0], bez);
// trace_bezier(closed, size=1); // trace_bezier(closed);
function bezier_offset(offset, bezier, N=3) = function bezier_offset(offset, bezier, N=3) =
assert(is_vector(offset,2)) assert(is_vector(offset,2))
assert(is_path(bezier,2), "bezier_offset() can only work on 2D bezier paths.") 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], // [80,-50], [50,-50], [30,-50],
// [5,-30], [0,0] // [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); // linear_extrude(height=0.1) bezier_polygon(bez, N=3);
module bezier_polygon(bezier, splinesteps=16, N=3) { module bezier_polygon(bezier, splinesteps=16, N=3) {
assert(is_path(bezier,2), "bezier_polygon() can only work on 2D bezier paths."); 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], // [ 14, -5], [ 15, 0], [16, 5],
// [ 5, 10], [ 0, 10] // [ 5, 10], [ 0, 10]
// ]; // ];
// trace_bezier(bez, N=3, size=0.5); // trace_bezier(bez, N=3, width=0.5);
module trace_bezier(bez, size=1, N=3) { module trace_bezier(bez, width=1, N=3) {
assert(is_path(bez)); assert(is_path(bez));
assert(is_int(N)); 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.")); 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"); $fn=8;
trace_path(bezier_path(bez, N=N), size=size, color="cyan"); 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 // Section: Patch Functions

View file

@ -109,7 +109,7 @@ module move_copies(a=[[0,0,0]])
// cube(size=[1,3,1],center=true); // cube(size=[1,3,1],center=true);
// cube(size=[3,1,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); // pts = line_of([10,5],n=5);
// move_copies(pts) circle(d=2); // move_copies(pts) circle(d=2);
module line_of(spacing, n, l, p1, p2) module line_of(spacing, n, l, p1, p2)

View file

@ -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 // Section: Computing paths
// Function&Module: arc() // 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); // stroke(closed=true, path);
// Example(FlatSpin,VPD=175): // Example(FlatSpin,VPD=175):
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]); // 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) = 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)) assert(is_bool(endpoint))
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true") !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 // d1 = Diameter of bottom of helix
// d2 = Diameter of top of helix // d2 = Diameter of top of helix
// Example(3D): // 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 // 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) // Example(3D): Flat helix (note points are still 3d)
// stroke(helix(h=0,r1=50,r2=25,l=0, turns=4)); // stroke(helix(h=0,r1=50,r2=25,l=0, turns=4));
function helix(l,h,turns,angle, r, r1, r2, d, d1, d2)= function helix(l,h,turns,angle, r, r1, r2, d, d1, d2)=

View file

@ -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 // 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 // will have one extra point because of the final connecting segment that connects the last
// point of the path to the first point. // 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) = function path_length_fractions(path, closed=false) =
assert(is_path(path)) assert(is_path(path))
assert(is_bool(closed)) 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]] /// // 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); /// stroke(path, closed=true, width=1);
/// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10); /// 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( let(
path = closed ? close_path(path,eps=eps) : path, path = closed ? close_path(path,eps=eps) : path,
plen = len(path) plen = len(path)
) )
[ for (i = [0:1:plen-3]) let( [ for (i = [0:1:plen-3]) let(
a1 = path[i], a1 = path[i],
a2 = path[i+1], a2 = path[i+1],
// The sign of signals is positive if the segment is one one side of seg_normal = unit([-(a2-a1).y, (a2-a1).x],[0,0]),
// the line defined by [a1,a2] and negative on the other side. vals = path*seg_normal,
seg_normal = unit([-(a2-a1).y, (a2-a1).x]), ref = a1*seg_normal,
signals = [for(j=[i+2:1:plen-(i==0 && closed? 2: 1)]) path[j]-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)]) for(j=[i+2:1:plen-(i==0 && closed? 3: 2)])
// The signals test requires the two signals to have different signs, if( signals[j-i-2]*signals[j-i-1]<=0 ) let( // segm [b1,b2] intersects line [a1,a2]
// otherwise b1 and b2 are on the same side of the line defined by [a1,a2] b1 = path[j],
// and hence intersection is impossible b2 = path[j+1],
if( signals[j-i-2]*signals[j-i-1] <= 0 ) isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)
let( )
b1 = path[j], if (isect
b2 = path[j+1] && isect[1]> (i==0 && !closed? -eps: 0)
) && isect[1]<= 1+eps
// This test checks that a1 and a2 are on opposite sides of the && isect[2]> 0
// line defined by [b1,b2]. && isect[2]<= 1+eps)
if( cross(b2-b1, a1-b1)*cross(b2-b1, a2-b1) <= 0 ) [isect[0], i, isect[1], j, isect[2]]
let(isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps))
if (isect) [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. // maxlen = The maximum allowed path segment length.
// --- // ---
// closed = If true, treat path like a closed polygon. Default: true // closed = If true, treat path like a closed polygon. Default: true
// Example: // Example(2D):
// path = pentagon(d=100); // path = pentagon(d=100);
// spath = subdivide_long_segments(path, 10, closed=true); // spath = subdivide_long_segments(path, 10, closed=true);
// stroke(path); // stroke(path);
@ -487,14 +496,14 @@ function path_closest_point(path, pt) =
// path = path to find the tagent vectors for // path = path to find the tagent vectors for
// closed = set to true of the path is closed. Default: false // closed = set to true of the path is closed. Default: false
// uniform = set to false to correct for non-uniform sampling. Default: true // 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]); // rect = square([10,3]);
// tangents = path_tangents(rect,closed=true); // tangents = path_tangents(rect,closed=true);
// stroke(rect,closed=true, width=0.1); // stroke(rect,closed=true, width=0.1);
// color("purple") // color("purple")
// for(i=[0:len(tangents)-1]) // for(i=[0:len(tangents)-1])
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); // 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]); // rect = square([10,3]);
// tangents = path_tangents(rect,closed=true,uniform=false); // tangents = path_tangents(rect,closed=true,uniform=false);
// stroke(rect,closed=true, width=0.1); // 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); // left(100)region(outside);
// rainbow(outside) // rainbow(outside)
// stroke($item,closed=true); // stroke($item,closed=true);
// Example: // Example(2D):
// N=12; // N=12;
// ang=360/N; // ang=360/N;
// sr=10; // sr=10;
@ -1033,19 +1042,19 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// "move", sr]); // "move", sr]);
// stroke(path, width=.3); // stroke(path, width=.3);
// right(20)rainbow(polygon_parts(path)) polygon($item); // 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]]; // path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]];
// stroke(path,width=0.3); // stroke(path,width=0.3);
// right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true); // 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"]]); // path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]);
// back(2)stroke(path,width=.3); // back(2)stroke(path,width=.3);
// fwd(12)rainbow(polygon_parts(path)) polygon($item); // 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"]]); // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]);
// polygon(path); // polygon(path);
// right(22)rainbow(polygon_parts(path)) polygon($item); // 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"]]); // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]);
// polygon(path); // polygon(path);
// right(27)rainbow(polygon_parts(path)) polygon($item); // right(27)rainbow(polygon_parts(path)) polygon($item);

View file

@ -113,7 +113,7 @@ module region(r)
// Function: point_in_region() // Function: point_in_region()
// Usage: // Usage:
// check = point_in_region(point, region); // check = point_in_region(point, region, [eps]);
// Description: // Description:
// Tests if a point is inside, outside, or on the border of a region. // Tests if a point is inside, outside, or on the border of a region.
// Returns -1 if the point is outside the 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]; [for(s=subpaths) if (len(s)>1) s];
// Function: split_nested_region() // Function: region_parts()
// Usage: // Usage:
// rgns = split_nested_region(region); // rgns = region_parts(region);
// Description: // Description:
// Separates the distinct (possibly nested) positive subregions of a larger compound region. // Divides a region into a list of connected regions. Each connected region has exactly one outside boundary
// Returns a list of regions, such that each returned region has exactly one positive outline // and zero or more outlines defining internal holes. Note that behavior is undefined on invalid regions whose
// and zero or more void outlines. // components intersect each other.
function split_nested_region(region) = // 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( let(
paths = sort(idx=0, [ paths = sort(idx=0, [
for(i = idx(region)) let( 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` // vnf = If given, the faces are added to this VNF. Default: `EMPTY_VNF`
function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) = function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
let ( let (
regions = split_nested_region(region), regions = region_parts(region),
vnfs = [ vnfs = [
if (vnf != EMPTY_VNF) vnf, if (vnf != EMPTY_VNF) vnf,
for (rgn = regions) let( 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( let(
anchor = get_anchor(anchor,center,BOT,BOT), anchor = get_anchor(anchor,center,BOT,BOT),
region = is_path(region)? [region] : region, region = is_path(region)? [region] : region,
cp = mean(pointlist_bounds(flatten(region))), cp = mean(pointlist_bounds(flatten(region))),
regions = split_nested_region(region), regions = region_parts(region),
slices = default(slices, floor(twist/5+1)), slices = default(slices, floor(twist/5+1)),
step = twist/slices, step = twist/slices,
hstep = height/slices, hstep = height/slices,
@ -939,7 +952,8 @@ function union(regions=[],b=undef,c=undef,eps=EPSILON) =
// color("green") region(difference(shape1,shape2)); // color("green") region(difference(shape1,shape2));
function difference(regions=[],b=undef,c=undef,eps=EPSILON) = function difference(regions=[],b=undef,c=undef,eps=EPSILON) =
b!=undef? difference(concat([regions],[b],c==undef?[]:[c]), eps=eps) : 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( difference(
let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)]) let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)])
concat( 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); // for (shape = [shape1,shape2]) color("red") stroke(shape, width=0.5, closed=true);
// color("green") region(intersection(shape1,shape2)); // color("green") region(intersection(shape1,shape2));
function intersection(regions=[],b=undef,c=undef,eps=EPSILON) = function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
b!=undef? intersection(concat([regions],[b],c==undef?[]:[c]),eps=eps) : b!=undef? intersection(concat([regions],[b],c==undef?[]:[c]),eps=eps)
len(regions)<=1? regions[0] : : len(regions)==0 ? []
intersection( : len(regions)==1? regions[0]
let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)]) : let(regions=[for (r=regions) quant(is_path(r)? [r] : r, 1/65536)])
concat( intersection([
[_tagged_region(regions[0],regions[1],["I","S"],["I"],eps=eps)], _tagged_region(regions[0],regions[1],["I","S"],["I"],eps=eps),
[for (i=[2:1:len(regions)-1]) regions[i]] for (i=[2:1:len(regions)-1]) regions[i]
), ],
eps=eps eps=eps
); );
// Function&Module: exclusive_or() // Function&Module: exclusive_or()
@ -989,16 +1004,17 @@ function intersection(regions=[],b=undef,c=undef,eps=EPSILON) =
// Description: // Description:
// When called as a function and given a list of regions, where each region is a list of closed // 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. // 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: // Arguments:
// regions = List of regions to exclusive_or. Each region is a list of closed paths. // 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)); // shape1 = move([-8,-8,0], p=circle(d=50));
// shape2 = move([ 8, 8,0], p=circle(d=50)); // shape2 = move([ 8, 8,0], p=circle(d=50));
// for (shape = [shape1,shape2]) // for (shape = [shape1,shape2])
// color("red") stroke(shape, width=0.5, closed=true); // color("red") stroke(shape, width=0.5, closed=true);
// color("green") region(exclusive_or(shape1,shape2)); // 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() { // exclusive_or() {
// square(40,center=false); // square(40,center=false);
// circle(d=40); // circle(d=40);

View file

@ -9,6 +9,10 @@
// of the shortcuts can return a matrix representing the operation // of the shortcuts can return a matrix representing the operation
// the shortcut performs. The rotation and scaling shortcuts accept // the shortcut performs. The rotation and scaling shortcuts accept
// an optional centerpoint for the rotation or scaling operation. // 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: // Includes:
// include <BOSL2/std.scad> // include <BOSL2/std.scad>
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -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 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 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. // * 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: // 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` // 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 // Example: Scaling Points
// path = xrot(90,p=path3d(circle(d=50,$fn=12))); // path = xrot(90,p=path3d(circle(d=50,$fn=12)));
// #trace_path(path); // #stroke(path,closed=true);
// trace_path(zscale(2,p=path)); // stroke(zscale(2,path),closed=true);
module zscale(z=1, p, cp=0) { module zscale(z=1, p, cp=0) {
assert(is_undef(p), "Module form `zscale()` does not accept p= argument."); assert(is_undef(p), "Module form `zscale()` does not accept p= argument.");
cp = is_num(cp)? [0,0,cp] : cp; cp = is_num(cp)? [0,0,cp] : cp;
@ -1119,15 +1124,23 @@ function zflip(p, z=0) =
// x = Destination 3D vector for x axis. // x = Destination 3D vector for x axis.
// y = Destination 3D vector for y axis. // y = Destination 3D vector for y axis.
// z = Destination 3D vector for z 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 // reverse = reverse direction of the map for orthogonal inputs. Default: false
// Example: Remap axes after linear extrusion // Example: Remap axes after linear extrusion
// frame_map(x=[0,1,0], y=[0,0,1]) linear_extrude(height=10) square(3); // 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 // 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 // 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]); // 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] // 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); // 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) = function frame_map(x,y,z, p, reverse=false) =
is_def(p) is_def(p)
? apply(frame_map(x,y,z,reverse=reverse), 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); // color("blue") move_copies(pts) circle(d=3, $fn=8);
// Example(FlatSpin,VPD=175): Calling as a 3D Function // Example(FlatSpin,VPD=175): Calling as a 3D Function
// pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3); // 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) 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.") assert(is_undef(p), "Module form `skew()` does not accept p= argument.")

View file

@ -202,16 +202,17 @@ function vnf_vertex_array(
// Description: // 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 // 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. // 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: // Arguments:
// points = List of point lists for each row // 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. // 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 // 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]]]; // pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]];
// vnf = vnf_tri_array(pts); // vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1); // vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9); // 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]]]; // pts = [for(y=[0:2:10]) [for(x=[-y/2:y/2]) [x,y,y]]];
// vnf = vnf_tri_array(pts); // vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1); // vnf_wireframe(vnf,width=0.1);
@ -230,7 +231,7 @@ function vnf_vertex_array(
// vnf=vnf_tri_array(pts2)); // vnf=vnf_tri_array(pts2));
// color("green")vnf_wireframe(vnf,width=0.1); // color("green")vnf_wireframe(vnf,width=0.1);
// vnf_polyhedron(vnf); // 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]; // 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])]; // pts = [for(y=idx(lens)) lerpn([-lens[y],y,y],[lens[y],y,y],lens[y])];
// vnf = vnf_tri_array(pts); // vnf = vnf_tri_array(pts);
@ -283,9 +284,8 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
// Description: // Description:
// Given a list of VNF structures, merges them all into a single VNF structure. // 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`, // When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`,
// drops unreferenced vertices and any final face with less than 3 vertices. // and eliminates any faces with fewer than 3 vertices.
// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex // (Unreferenced vertices of the input VNFs are not dropped.)
// are not dropped.
// Arguments: // Arguments:
// vnfs - a list of the VNFs to merge in one VNF. // vnfs - a list of the VNFs to merge in one VNF.
// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false // cleanup - when true, consolidates the duplicate vertices of the merge. Default: false