diff --git a/drawing.scad b/drawing.scad index abf2fcd..355c351 100644 --- a/drawing.scad +++ b/drawing.scad @@ -606,6 +606,8 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { // path=arc(n, cp=, points=[P0,P1], [long=], [cw=], [ccw=]); // Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`. // path=arc(n, points=[P0,P1,P2]); +// Usage: 2D or 3D arc, fron tangent point on segment `[P0,P1]` to the tangent point on segment `[P1,P2]`. +// path=arc(n, corner=[P0,P1,P2], r=); // Usage: as module // arc(...) [ATTACHMENTS]; // Topics: Paths (2D), Paths (3D), Shapes (2D), Path Generators @@ -620,6 +622,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { // d = Diameter of the arc. // cp = Centerpoint of arc. // points = Points on the arc. +// corner = A path of two segments to fit an arc tangent to. // long = if given with cp and points takes the long arc instead of the default short arc. Default: false // cw = if given with cp and 2 points takes the arc in the clockwise direction. Default: false // ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction. Default: false @@ -641,6 +644,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { // arc(width=60, thickness=20); // arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true); // arc(points=[[30,-5],[20,10],[-10,20]], wedge=true); +// Example(2D): Fit to three points. // arc(points=[[5,30],[-10,-10],[30,5]], wedge=true); // Example(2D): // path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true); @@ -648,18 +652,25 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { // Example(FlatSpin,VPD=175): // path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]); // 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) = +// Example(2D): Fit to a corner. +// pts = [[0,40], [-40,-10], [30,0]]; +// path = arc(corner=pts, r=20); +// stroke(pts, endcaps="arrow2"); +// stroke(path, endcap2="arrow2", color="blue"); +function arc(n, r, angle, d, cp, points, corner, 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") - list_head(arc(u_add(n,1),r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) : + !endpoint ? + assert(!wedge, "endpoint cannot be false if wedge is true") + list_head(arc(u_add(n,1),r,angle,d,cp,points,corner,width,thickness,start,wedge,long,cw,ccw,true)) : assert(is_undef(n) || (is_integer(n) && n>=2), "Number of points must be an integer 2 or larger") // First try for 2D arc specified by width and thickness is_def(width) && is_def(thickness)? ( - assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc") - assert(width>0, "Width must be postive") - assert(thickness>0, "Thickness must be positive") + assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc") + assert(width>0, "Width must be postive") + assert(thickness>0, "Thickness must be positive") arc(n,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge) - ) : is_def(angle)? ( + ) : + is_def(angle)? ( let( parmok = !any_defined([points,width,thickness]) && ((is_vector(angle,2) && is_undef(start)) || is_num(angle)) @@ -680,39 +691,71 @@ function arc(n, r, angle, d, cp, points, width, thickness, start, wedge=false, l extra = wedge? [cp] : [] ) concat(extra,arcpoints) + ) : is_def(corner)? ( + assert(is_path(corner,[2,3]),"Point list is invalid") + // Arc is 3D, so transform corner to 2D and make a recursive call, then remap back to 3D + len(corner[0]) == 3? ( + assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false") + assert(is_undef(cp) || is_vector(cp,3),"corner are 3d so cp must be 3d") + let( + plane = [is_def(cp) ? cp : corner[2], corner[0], corner[1]], + center2d = is_def(cp) ? project_plane(plane,cp) : undef, + points2d = project_plane(plane, corner) + ) + lift_plane(plane,arc(n,cp=center2d,corner=points2d,wedge=wedge,long=long)) + ) : + assert(is_path(corner) && len(corner) == 3) + let(col = is_collinear(corner[0],corner[1],corner[2])) + assert(!col, "Collinear inputs do not define an arc") + let( r = get_radius(r=r, d=d) ) + assert(is_finite(r) && r>0, "Must specify r= or d= when corner= is given.") + let( + ci = circle_2tangents(corner[0], corner[1], corner[2], r=r, tangents=true), + cp = ci[0], nrm = ci[1], tp1 = ci[2], tp2 = ci[3], + dir = det2([corner[1]-corner[0],corner[2]-corner[1]]) > 0, + corner = dir? [tp1,tp2] : [tp2,tp1], + theta_start = atan2(corner[0].y-cp.y, corner[0].x-cp.x), + theta_end = atan2(corner[1].y-cp.y, corner[1].x-cp.x), + angle = posmod(theta_end-theta_start, 360), + arcpts = arc(n,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge) + ) + dir ? arcpts : reverse(arcpts) ) : - assert(is_path(points,[2,3]),"Point list is invalid") - // Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D - len(points[0])==3? ( - assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false") - assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d") + assert(is_path(points,[2,3]),"Point list is invalid") + // Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D + len(points[0]) == 3? ( + assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false") + assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d") let( plane = [is_def(cp) ? cp : points[2], points[0], points[1]], center2d = is_def(cp) ? project_plane(plane,cp) : undef, points2d = project_plane(plane, points) ) lift_plane(plane,arc(n,cp=center2d,points=points2d,wedge=wedge,long=long)) - ) : is_def(cp)? ( + ) : + is_def(cp)? ( // Arc defined by center plus two points, will have radius defined by center and points[0] // and extent defined by direction of point[1] from the center - assert(is_vector(cp,2), "Centerpoint must be a 2d vector") - assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed") - assert(points[0]!=points[1], "Arc endpoints are equal") - assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint") - assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long)) + assert(is_vector(cp,2), "Centerpoint must be a 2d vector") + assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed") + assert(points[0]!=points[1], "Arc endpoints are equal") + assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint") + assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long)) let( angle = vector_angle(points[0], cp, points[1]), v1 = points[0]-cp, v2 = points[1]-cp, - prelim_dir = sign(det2([v1,v2])), // z component of cross product - dir = prelim_dir != 0 - ? prelim_dir - : assert(cw || ccw, "Collinear inputs don't define a unique arc") - 1, - r=norm(v1), - final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle + prelim_dir = sign(det2([v1,v2])), // z component of cross product + dir = prelim_dir != 0 ? prelim_dir : + assert(cw || ccw, "Collinear inputs don't define a unique arc") + 1, + r = norm(v1), + final_angle = long || (ccw && dir<0) || (cw && dir>0) ? + -dir*(360-angle) : + dir*angle, + sa = atan2(v1.y,v1.x) ) - arc(n,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge) + arc(n,cp=cp,r=r,start=sa,angle=final_angle,wedge=wedge) ) : ( // Final case is arc passing through three points, starting at point[0] and ending at point[3] let(col = is_collinear(points[0],points[1],points[2])) @@ -732,9 +775,9 @@ function arc(n, r, angle, d, cp, points, width, thickness, start, wedge=false, l ); -module arc(n, r, angle, d, cp, points, width, thickness, start, wedge=false, anchor=CENTER, spin=0) +module arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=false, anchor=CENTER, spin=0) { - path = arc(n=n, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge); + path = arc(n=n, r=r, angle=angle, d=d, cp=cp, points=points, corner=corner, width=width, thickness=thickness, start=start, wedge=wedge); attachable(anchor,spin, two_d=true, path=path, extent=false) { polygon(path); children(); diff --git a/geometry.scad b/geometry.scad index 59eaa0f..da26d96 100644 --- a/geometry.scad +++ b/geometry.scad @@ -865,15 +865,45 @@ function _is_point_above_plane(plane, point) = // Topics: Geometry, Circles, Lines, Intersection // Description: // Find intersection points between a 2d circle and a line, ray or segment specified by two points. -// By default the line is unbounded. +// By default the line is unbounded. Returns the list of zero or more intersection points. // Arguments: -// c = center of circle -// r = radius of circle -// line = two points defining the line -// bounded = false for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false +// c = Center of circle +// r = Radius of circle +// line = Two points defining the line +// bounded = False for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false // --- -// d = diameter of circle -// eps = epsilon used for identifying the case with one solution. Default: 1e-9 +// d = Diameter of circle +// eps = Epsilon used for identifying the case with one solution. Default: `1e-9` +// Example(2D): Standard intersection returns two points. +// line = [[-15,2], [15,7]]; +// cp = [1,2]; r = 10; +// translate(cp) circle(r=r); +// color("black") stroke(line, endcaps="arrow2", width=0.5); +// isects = circle_line_intersection(c=cp, r=r, line=line); +// color("red") move_copies(isects) circle(d=1); +// Example(2D): Tangent intersection returns one point. +// line = [[-10,12], [10,12]]; +// cp = [1,2]; r = 10; +// translate(cp) circle(r=r); +// color("black") stroke(line, endcaps="arrow2", width=0.5); +// isects = circle_line_intersection(c=cp, r=r, line=line); +// color("#f44") move_copies(isects) circle(d=1); +// Example(2D): A bounded ray might only intersect in one direction. +// line = [[-5,2], [5,7]]; +// extended = [line[0], line[0]+22*unit(line[1]-line[0])]; +// cp = [1,2]; r = 10; +// translate(cp) circle(r=r); +// color("gray") dashed_stroke(extended, width=0.2); +// color("black") stroke(line, endcap2="arrow2", width=0.5); +// isects = circle_line_intersection(c=cp, r=r, line=line, bounded=[true,false]); +// color("#f44") move_copies(isects) circle(d=1); +// Example(2D): If they don't intersect at all, then an empty list is returned. +// line = [[-12,12], [12,8]]; +// cp = [-5,-2]; r = 10; +// translate(cp) circle(r=r); +// color("black") stroke(line, endcaps="arrow2", width=0.5); +// isects = circle_line_intersection(c=cp, r=r, line=line); +// color("#f44") move_copies(isects) circle(d=1); function circle_line_intersection(c,r,line,bounded=false,d,eps=EPSILON) = assert(_valid_line(line,2), "Invalid 2d line.") assert(is_vector(c,2), "Circle center must be a 2-vector") @@ -968,32 +998,46 @@ function circle_circle_intersection(c1,r1,c2,r2,eps=EPSILON,d1,d2) = [L*a+h*b+c1, L*a-h*b+c1]; -// Function&Module: circle_2tangents() -// Usage: As Function +// Function: circle_2tangents() +// Usage: // circ = circle_2tangents(pt1, pt2, pt3, r|d=, [tangents=]); // Topics: Geometry, Circles, Tangents -// Usage: As Module -// circle_2tangents(pt1, pt2, pt3, r|d=, [h=], [center=]); // 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`. // . -// 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]`. +// When called with collinear rays, returns `undef`. +// Otherwise, when called with `tangents=false`, returns `[CP,NORMAL]`. +// Otherwise, when called with `tangents=true`, returns `[CP,NORMAL,TANPT1,TANPT2]`. // - 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]`. // - TANPT2 is the point where the circle is tangent to the ray `[pt2,pt3]`. -// - ANG1 is the angle from the ray `[CP,pt2]` to the ray `[CP,TANPT1]` -// - ANG2 is the angle from the ray `[CP,pt2]` to the ray `[CP,TANPT2]` +// Figure(Med,NoAxes): +// pts = [[45,10,-5], [10,5,10], [15,40,5]]; +// rad = 15; +// circ = circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, tangents=true); +// cp = circ[0]; n = circ[1]; tp1 = circ[2]; tp2 = circ[3]; +// color("yellow") stroke(pts, endcaps="arrow2"); +// color("purple") move_copies([cp,tp1,tp2]) sphere(d=2, $fn=12); +// color("lightgray") stroke([cp,tp2], width=0.5); +// stroke([cp,cp+n*20], endcap2="arrow2"); +// labels = [ +// ["pt1", "blue", 2.5, [ 4, 0, 1], pts[0]], +// ["pt2", "blue", 2.5, [-4, 0,-3], pts[1]], +// ["pt3", "blue", 2.5, [ 4, 0, 1], pts[2]], +// ["r", "blue", 2.5, [ 0,-2, 2], (cp+tp2)/2], +// ["CP", "brown", 2.5, [ 6,-4, 3], cp], +// ["Normal", "brown", 2.0, [ 5, 2, 1], cp+20*n], +// ["TanPt1", "brown", 2.0, [-5,-4, 0], tp1], +// ["TanPt2", "brown", 2.0, [-5, 0, 2], tp2], +// ]; +// for(l=labels) +// color(l[1]) move(l[4]+l[3]) rot($vpr) +// linear_extrude(height=0.1) +// text(text=l[0], size=l[2], halign="center", valign="center"); +// color("green",0.5) move(cp) cyl(h=0.1, r=rad, orient=n, $fn=36); // Arguments: // pt1 = A point that the first ray passes though. // pt2 = The starting point of both rays. @@ -1001,40 +1045,31 @@ function circle_circle_intersection(c1,r1,c2,r2,eps=EPSILON,d1,d2) = // 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"); +// pts = [[40,40], [10,10], [55,5]]; rad = 10; // 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); -// stroke([[0,0],rad*[cos(315),sin(315)]]); -// } -// } -// move_copies(pts) color("blue") circle(d=2, $fn=12); -// 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"); +// stroke(pts, endcaps="arrow2"); +// color("red") move(circ[0]) circle(r=rad); // 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); +// pts = [[20,40], [10,10], [55,20]]; rad = 10; +// circ = circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, tangents=true); +// stroke(pts, endcaps="arrow2"); +// color("red") move(circ[0]) circle(r=rad); +// color("blue") move_copies(select(circ,2,3)) circle(d=2); +// Example: Fit into 3D path corner. +// pts = [[45,5,10], [10,10,15], [30,40,30]]; rad = 10; +// circ = circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad); +// stroke(pts, endcaps="arrow2"); +// color("red") move(circ[0]) cyl(h=10, r=rad, orient=circ[1]); +// Example: +// path = yrot(20, p=path3d(star(d=100, n=5, step=2))); +// stroke(path, closed=true); +// for (i = [0:1:5]) { +// crn = select(path, i*2-1, i*2+1); +// ci = circle_2tangents(crn[0], crn[1], crn[2], r=5); +// move(ci[0]) cyl(h=10,r=5,,orient=ci[1]); +// } 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.") @@ -1057,39 +1092,16 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = let( x = hyp * cos(a/2), tp1 = pt2 + x * v1, - tp2 = pt2 + x * v2, - dang1 = vector_angle(tp1-cp,pt2-cp), - dang2 = vector_angle(tp2-cp,pt2-cp) + tp2 = pt2 + x * v2 ) - [cp, n, tp1, tp2, dang1, dang2]; + [cp, n, tp1, tp2]; -module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) { - no_children($children); - 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&Module: circle_3points() -// Usage: As Function +// Function: circle_3points() +// Usage: // circ = circle_3points(pt1, pt2, pt3); // circ = circle_3points([pt1, pt2, pt3]); // Topics: Geometry, Circles -// Usage: As Module -// circle_3points(pt1, pt2, pt3, [h], [center]); -// circle_3points([pt1, pt2, pt3], [h], [center]); // 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). @@ -1103,26 +1115,12 @@ module circle_2tangents(pt1, pt2, pt3, r, d, h, center=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 = 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); -// 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)) ? circle_3points(pt1[0], pt1[1], pt1[2]) @@ -1149,19 +1147,6 @@ function circle_3points(pt1, pt2, pt3) = ) [ cp, r, n ]; -module circle_3points(pt1, pt2, pt3, h, center=false) { - no_children($children); - 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: @@ -1339,6 +1324,13 @@ function _noncollinear_triple(points,error=true,eps=EPSILON) = // --- // d = diameter of sphere // eps = epsilon used for identifying the case with one solution. Default: 1e-9 +// Example: +// cp = [10,20,5]; r = 40; +// line = [[-50,-10,25], [70,0,40]]; +// isects = sphere_line_intersection(c=cp, r=r, line=line); +// color("cyan") stroke(line); +// move(cp) sphere(r=r, $fn=72); +// color("red") move_copies(isects) sphere(d=3, $fn=12); function sphere_line_intersection(c,r,line,bounded=false,d,eps=EPSILON) = assert(_valid_line(line,3), "Invalid 3d line.") assert(is_vector(c,3), "Sphere center must be a 3-vector") @@ -1396,6 +1388,15 @@ function polygon_area(poly, signed=false) = // Arguments: // object = object to compute the centroid of // eps = epsilon value for identifying degenerate cases +// Example(2D): +// path = [ +// [-10,10], [-5,15], [15,15], [20,0], +// [15,-5], [25,-20], [25,-27], [15,-20], +// [0,-30], [-15,-25], [-5,-5] +// ]; +// linear_extrude(height=0.01) polygon(path); +// cp = centroid(path); +// color("red") move(cp) sphere(d=2); function centroid(object,eps=EPSILON) = assert(is_finite(eps) && (eps>=0), "The tolerance should a non-negative value." ) is_vnf(object) ? _vnf_centroid(object,eps) @@ -1465,6 +1466,13 @@ function _polygon_centroid(poly, eps=EPSILON) = // the the result is undefined. It doesn't check for coplanarity. // Arguments: // poly = The list of 3D path points for the perimeter of the polygon. +// Example: +// path = rot([0,30,15], p=path3d(star(n=5, d=100, step=2))); +// stroke(path, closed=true); +// n = polygon_normal(path); +// rot(from=UP, to=n) +// color("red") +// stroke([[0,0,0], [0,0,20]], endcap2="arrow2"); function polygon_normal(poly) = assert(is_path(poly,dim=3), "Invalid 3D polygon." ) let( diff --git a/shapes2d.scad b/shapes2d.scad index 08a2977..9e97678 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -204,12 +204,18 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0) // Topics: Shapes (2D), Path Generators (2D) // Usage: As a Module // circle(r|d=, ...) [ATTACHMENTS]; +// circle(points=) [ATTACHMENTS]; +// circle(r|d=, corner=) [ATTACHMENTS]; // Usage: As a Function // path = circle(r|d=, ...); +// path = circle(points=); +// path = circle(r|d=, corner=); // See Also: ellipse(), circle_2tangents(), circle_3points() // Description: // When called as the builtin module, creates a 2D polygon that approximates a circle of the given size. // When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size. +// If `corner=` is given three 2D points, centers the circle so that it will be tangent to both segments of the path, on the inside corner. +// If `points=` is given three 2D points, centers and sizes the circle so that it passes through all three points. // Arguments: // r = The radius of the circle to create. // d = The diameter of the circle to create. @@ -220,20 +226,78 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0) // circle(r=25); // Example(2D): By Diameter // circle(d=50); -// Example(NORENDER): Called as Function +// Example(2D): Fit to Three Points +// pts = [[50,25], [25,-25], [-10,0]]; +// circle(points=pts); +// color("red") move_copies(pts) circle(); +// Example(2D): Fit Tangent to Inside Corner of Two Segments +// path = [[50,25], [-10,0], [25,-25]]; +// circle(corner=path, r=15); +// color("red") stroke(path); +// Example(2D): Called as Function // path = circle(d=50, anchor=FRONT, spin=45); -function circle(r, d, anchor=CENTER, spin=0) = +// stroke(path); +function circle(r, d, points, corner, anchor=CENTER, spin=0) = + assert(is_undef(corner) || (is_path(corner,[2]) && len(corner) == 3)) + assert(is_undef(points) || is_undef(corner), "Cannot specify both points and corner.") let( - r = get_radius(r=r, d=d, dflt=1), + data = is_def(points)? + assert(is_path(points,[2]) && len(points) == 3) + assert(is_undef(corner), "Cannot specify corner= when points= is given.") + assert(is_undef(r) && is_undef(d), "Cannot specify r= or d= when points= is given.") + let( c = circle_3points(points) ) + assert(!is_undef(c[0]), "Points cannot be collinear.") + let( cp = c[0], r = c[1] ) + [cp, r] : + is_def(corner)? + assert(is_path(corner,[2]) && len(corner) == 3) + assert(is_undef(points), "Cannot specify points= when corner= is given.") + let( + r = get_radius(r=r, d=d, dflt=1), + c = circle_2tangents(pt1=corner[0], pt2=corner[1], pt3=corner[2], r=r) + ) + assert(c!=undef, "Corner path cannot be collinear.") + let( cp = c[0] ) + [cp, r] : + let( + cp = [0, 0], + r = get_radius(r=r, d=d, dflt=1) + ) [cp, r], + cp = data[0], + r = data[1], sides = segs(r), - path = [for (i=[0:1:sides-1]) let(a=360-i*360/sides) r*[cos(a),sin(a)]] + path = [for (i=[0:1:sides-1]) let(a=360-i*360/sides) r*[cos(a),sin(a)]+cp] ) reorient(anchor,spin, two_d=true, r=r, p=path); -module circle(r, d, anchor=CENTER, spin=0) { - r = get_radius(r=r, d=d, dflt=1); - attachable(anchor,spin, two_d=true, r=r) { - _circle(r=r); - children(); +module circle(r, d, points, corner, anchor=CENTER, spin=0) { + if (is_path(points)) { + c = circle_3points(points); + check = assert(c!=undef && c[0] != undef, "Points must not be collinear."); + cp = c[0]; + r = c[1]; + translate(cp) { + attachable(anchor,spin, two_d=true, r=r) { + _circle(r=r); + children(); + } + } + } else if (is_path(corner)) { + r = get_radius(r=r, d=d, dflt=1); + c = circle_2tangents(pt1=corner[0], pt2=corner[1], pt3=corner[2], r=r); + check = assert(c != undef && c[0] != undef, "Points must not be collinear."); + cp = c[0]; + translate(cp) { + attachable(anchor,spin, two_d=true, r=r) { + _circle(r=r); + children(); + } + } + } else { + r = get_radius(r=r, d=d, dflt=1); + attachable(anchor,spin, two_d=true, r=r) { + _circle(r=r); + children(); + } } }