diff --git a/geometry.scad b/geometry.scad index b09c05b..cf78195 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1220,16 +1220,25 @@ function in_front_of_plane(plane, point) = // Section: Circle Calculations -// Function: find_circle_2tangents() -// Usage: -// find_circle_2tangents(pt1, pt2, pt3, r|d, ); +// Function&Module: circle_2tangents() +// Usage: As Function +// circ = circle_2tangents(pt1, pt2, pt3, r|d, ); +// Usage: As Module +// circle_2tangents(pt1, pt2, pt3, r|d, ,
); // Description: // Given a pair of rays with a common origin, and a known circle radius/diameter, finds // the centerpoint for the circle of that size that touches both rays tangentally. // Both rays start at `pt2`, one passing through `pt1`, and the other through `pt3`. -// If the rays given are collinear, `undef` is returned. Otherwise, if `tangents` is -// true, then `[CP,NORMAL]` is returned. If `tangents` is false, the more extended -// `[CP,NORMAL,TANPT1,TANPT2,ANG1,ANG2]` is returned +// . +// When called as a module with an `h` height argument, creates a 3D cylinder of `h` +// length at the found centerpoint, aligned with the found normal. +// . +// When called as a module with 2D data and no `h` argument, creates a 2D circle of +// the given radius/diameter, tangentially touching both rays. +// . +// When called as a function with collinear rays, returns `undef`. +// Otherwise, when called as a function with `tangents=false`, returns `[CP,NORMAL]`. +// Otherwise, when called as a function with `tangents=true`, returns `[CP,NORMAL,TANPT1,TANPT2,ANG1,ANG2]`. // - CP is the centerpoint of the circle. // - NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D). // - TANPT1 is the point where the circle is tangent to the ray `[pt2,pt1]`. @@ -1242,13 +1251,15 @@ function in_front_of_plane(plane, point) = // pt3 = A point that the second ray passes though. // r = The radius of the circle to find. // d = The diameter of the circle to find. +// h = Height of the cylinder to create, when called as a module. +// center = When called as a module, center the cylinder if true, Default: false // tangents = If true, extended information about the tangent points is calculated and returned. Default: false // Example(2D): // pts = [[60,40], [10,10], [65,5]]; // rad = 10; // stroke([pts[1],pts[0]], endcap2="arrow2"); // stroke([pts[1],pts[2]], endcap2="arrow2"); -// circ = find_circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad); +// circ = circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad); // translate(circ[0]) { // color("green") { // stroke(circle(r=rad),closed=true); @@ -1259,14 +1270,29 @@ function in_front_of_plane(plane, point) = // translate(circ[0]) color("red") circle(d=2, $fn=12); // labels = [[pts[0], "pt1"], [pts[1],"pt2"], [pts[2],"pt3"], [circ[0], "CP"], [circ[0]+[cos(315),sin(315)]*rad*0.7, "r"]]; // for(l=labels) translate(l[0]+[0,2]) color("black") text(text=l[1], size=2.5, halign="center"); -function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = +// Example(2D): +// pts = [[-5,25], [5,-25], [45,15]]; +// rad = 12; +// color("blue") stroke(pts, width=0.75, endcaps="arrow2"); +// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad); +// Example: Non-centered Cylinder +// pts = [[45,15,10], [5,-25,5], [-5,25,20]]; +// rad = 12; +// color("blue") stroke(pts, width=0.75, endcaps="arrow2"); +// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, h=10, center=false); +// Example: Non-centered Cylinder +// pts = [[45,15,10], [5,-25,5], [-5,25,20]]; +// rad = 12; +// color("blue") stroke(pts, width=0.75, endcaps="arrow2"); +// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, h=10, center=true); +function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = let(r = get_radius(r=r, d=d, dflt=undef)) assert(r!=undef, "Must specify either r or d.") assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3)) || (is_matrix([pt1,pt2,pt3]) && (len(pt1)==2 || len(pt1)==3) ), "Invalid input points." ) is_undef(pt2) - ? find_circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents) + ? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents) : collinear(pt1, pt2, pt3)? undef : let( v1 = unit(pt1 - pt2), @@ -1287,11 +1313,29 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = ) [cp, n, tp1, tp2, dang1, dang2]; +module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) { + c = circle_2tangents(pt1=pt1, pt2=pt2, pt3=pt3, r=r, d=d); + assert(!is_undef(c), "Cannot find circle when both rays are collinear."); + cp = c[0]; n = c[1]; + if (approx(point3d(cp).z,0) && approx(point2d(n),[0,0]) && is_undef(h)) { + translate(cp) circle(r=r, d=d); + } else { + assert(is_finite(h), "h argument required when result is not flat on the XY plane."); + translate(cp) { + rot(from=UP, to=n) { + cylinder(r=r, d=d, h=h, center=center); + } + } + } +} -// Function: find_circle_3points() -// Usage: -// find_circle_3points(pt1, pt2, pt3); -// find_circle_3points([pt1, pt2, pt3]); +// Function&Module: circle_3points() +// Usage: As Function +// circ = circle_3points(pt1, pt2, pt3); +// circ = circle_3points([pt1, pt2, pt3]); +// Usage: As Module +// circle_3points(pt1, pt2, pt3, ,
); +// circle_3points([pt1, pt2, pt3], ,
); // Description: // Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear // points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D). @@ -1305,16 +1349,30 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = // pt1 = The first point. // pt2 = The second point. // pt3 = The third point. +// h = Height of the cylinder to create, when called as a module. +// center = When called as a module, center the cylinder if true, Default: false // Example(2D): // pts = [[60,40], [10,10], [65,5]]; -// circ = find_circle_3points(pts[0], pts[1], pts[2]); +// circ = circle_3points(pts[0], pts[1], pts[2]); // translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72); // translate(circ[0]) color("red") circle(d=3, $fn=12); // move_copies(pts) color("blue") circle(d=3, $fn=12); -function find_circle_3points(pt1, pt2, pt3) = +// Example(2D): +// pts = [[30,40], [10,20], [55,30]]; +// circle_3points(pts[0], pts[1], pts[2]); +// move_copies(pts) color("blue") circle(d=3, $fn=12); +// Example: Non-Centered Cylinder +// pts = [[30,15,30], [10,20,15], [55,25,25]]; +// circle_3points(pts[0], pts[1], pts[2], h=10, center=false); +// move_copies(pts) color("cyan") sphere(d=3, $fn=12); +// Example: Centered Cylinder +// pts = [[30,15,30], [10,20,15], [55,25,25]]; +// circle_3points(pts[0], pts[1], pts[2], h=10, center=true); +// move_copies(pts) color("cyan") sphere(d=3, $fn=12); +function circle_3points(pt1, pt2, pt3) = (is_undef(pt2) && is_undef(pt3) && is_list(pt1)) - ? find_circle_3points(pt1[0], pt1[1], pt1[2]) - : assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) + ? circle_3points(pt1[0], pt1[1], pt1[2]) + : assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2, "Invalid point(s)." ) collinear(pt1,pt2,pt3)? [undef,undef,undef] : @@ -1330,12 +1388,25 @@ function find_circle_3points(pt1, pt2, pt3) = sc = plane_intersection( [ each e1, e1*pm[es[1]] ], // planes orthogonal to 2 edges [ each e2, e2*pm[es[2]] ], - [ each n, n*v[0] ] ) , // triangle plane - cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc: [sc.x, sc.y], + [ each n, n*v[0] ] + ), // triangle plane + cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y], r = norm(sc-v[0]) - ) - [ cp, r, n ]; - + ) [ cp, r, n ]; + + +module circle_3points(pt1, pt2, pt3, h, center=false) { + c = circle_3points(pt1, pt2, pt3); + assert(!is_undef(c[0]), "Points cannot be collinear."); + cp = c[0]; r = c[1]; n = c[2]; + if (approx(point3d(cp).z,0) && approx(point2d(n),[0,0]) && is_undef(h)) { + translate(cp) circle(r=r); + } else { + assert(is_finite(h)); + translate(cp) rot(from=UP,to=n) cylinder(r=r, h=h, center=center); + } +} + // Function: circle_point_tangents() // Usage: diff --git a/shapes.scad b/shapes.scad index b84d458..f970b6c 100644 --- a/shapes.scad +++ b/shapes.scad @@ -673,7 +673,7 @@ module cyl( ) [p1,p2] ) : !is_undef(fil2)? ( let( - cn = find_circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)), + cn = circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)), ang = fil2<0? phi : phi-180, steps = ceil(abs(ang)/360*segs(abs(fil2))), step = ang/steps, @@ -688,7 +688,7 @@ module cyl( ) [p1,p2] ) : !is_undef(fil1)? ( let( - cn = find_circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)), + cn = circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)), ang = fil1<0? 180-phi : -phi, steps = ceil(abs(ang)/360*segs(abs(fil1))), step = ang/steps, diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad index bf49f0f..5979e86 100644 --- a/tests/test_geometry.scad +++ b/tests/test_geometry.scad @@ -57,8 +57,8 @@ test_plane_intersection(); test_coplanar(); test_points_on_plane(); test_in_front_of_plane(); -test_find_circle_2tangents(); -test_find_circle_3points(); +test_circle_2tangents(); +test_circle_3points(); test_circle_point_tangents(); test_noncollinear_triple(); @@ -470,22 +470,22 @@ module test_segment_closest_point() { } *test_segment_closest_point(); -module test_find_circle_2tangents() { +module test_circle_2tangents() { //** missing tests with arg tangent=true - assert(approx(find_circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0])); - assert(approx(find_circle_2tangents([-10,10],[0,0],[-10,-10],r=10/sqrt(2))[0],[-10,0])); - assert(approx(find_circle_2tangents([-10,10],[0,0],[10,10],r=10/sqrt(2))[0],[0,10])); - assert(approx(find_circle_2tangents([-10,-10],[0,0],[10,-10],r=10/sqrt(2))[0],[0,-10])); - assert(approx(find_circle_2tangents([0,10],[0,0],[10,0],r=10)[0],[10,10])); - assert(approx(find_circle_2tangents([10,0],[0,0],[0,-10],r=10)[0],[10,-10])); - assert(approx(find_circle_2tangents([0,-10],[0,0],[-10,0],r=10)[0],[-10,-10])); - assert(approx(find_circle_2tangents([-10,0],[0,0],[0,10],r=10)[0],[-10,10])); - assert_approx(find_circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30)); + assert(approx(circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0])); + assert(approx(circle_2tangents([-10,10],[0,0],[-10,-10],r=10/sqrt(2))[0],[-10,0])); + assert(approx(circle_2tangents([-10,10],[0,0],[10,10],r=10/sqrt(2))[0],[0,10])); + assert(approx(circle_2tangents([-10,-10],[0,0],[10,-10],r=10/sqrt(2))[0],[0,-10])); + assert(approx(circle_2tangents([0,10],[0,0],[10,0],r=10)[0],[10,10])); + assert(approx(circle_2tangents([10,0],[0,0],[0,-10],r=10)[0],[10,-10])); + assert(approx(circle_2tangents([0,-10],[0,0],[-10,0],r=10)[0],[-10,-10])); + assert(approx(circle_2tangents([-10,0],[0,0],[0,10],r=10)[0],[-10,10])); + assert_approx(circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30)); } -*test_find_circle_2tangents(); +*test_circle_2tangents(); -module test_find_circle_3points() { +module test_circle_3points() { count = 200; coords = rands(-100,100,count,seed_value=888); radii = rands(10,100,count,seed_value=390); @@ -496,7 +496,7 @@ module test_find_circle_3points() { r = radii[i]; angs = sort(select(angles,i,i+2)); pts = [for (a=angs) cp+polar_to_xy(r,a)]; - res = find_circle_3points(pts); + res = circle_3points(pts); if (!approx(res[0], cp)) { echo(cp=cp, r=r, angs=angs); echo(pts=pts); @@ -521,7 +521,7 @@ module test_find_circle_3points() { r = radii[i]; angs = sort(select(angles,i,i+2)); pts = [for (a=angs) cp+polar_to_xy(r,a)]; - res = find_circle_3points(pts[0], pts[1], pts[2]); + res = circle_3points(pts[0], pts[1], pts[2]); if (!approx(res[0], cp)) { echo(cp=cp, r=r, angs=angs); echo(pts=pts); @@ -549,7 +549,7 @@ module test_find_circle_3points() { n = nrm.z<0? -nrm : nrm; angs = sort(select(angles,i,i+2)); pts = translate(cp,p=rot(from=UP,to=n,p=[for (a=angs) point3d(polar_to_xy(r,a))])); - res = find_circle_3points(pts); + res = circle_3points(pts); if (!approx(res[0], cp)) { echo(cp=cp, r=r, angs=angs, n=n); echo(pts=pts); @@ -576,7 +576,7 @@ module test_find_circle_3points() { n = nrm.z<0? -nrm : nrm; angs = sort(select(angles,i,i+2)); pts = translate(cp,p=rot(from=UP,to=n,p=[for (a=angs) point3d(polar_to_xy(r,a))])); - res = find_circle_3points(pts[0], pts[1], pts[2]); + res = circle_3points(pts[0], pts[1], pts[2]); if (!approx(res[0], cp)) { echo(cp=cp, r=r, angs=angs, n=n); echo(pts=pts); @@ -597,7 +597,7 @@ module test_find_circle_3points() { } } } -*test_find_circle_3points(); +*test_circle_3points(); module test_circle_point_tangents() { diff --git a/version.scad b/version.scad index ba38fd3..33f39d8 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,429]; +BOSL_VERSION = [2,0,430]; // Section: BOSL Library Version Functions diff --git a/walls.scad b/walls.scad index e87b6f6..ebd22ce 100644 --- a/walls.scad +++ b/walls.scad @@ -93,10 +93,10 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, braces=false, strut, wall, an wall = is_num(wall)? wall : thick/2; bevel_h = strut + (thick-wall)/2/tan(ang); - cp1 = find_circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=strut)[0]; - cp2 = find_circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=bevel_h)[0]; - cp3 = find_circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=bevel_h)[0]; - cp4 = find_circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=strut)[0]; + cp1 = circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=strut)[0]; + cp2 = circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=bevel_h)[0]; + cp3 = circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=bevel_h)[0]; + cp4 = circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=strut)[0]; z1 = h/2; z2 = cp1.z;