Added corner= and points= to circle(), arc().

This commit is contained in:
Garth Minette 2022-04-06 00:21:57 -07:00
parent 4404dfde45
commit 837358d44c
3 changed files with 258 additions and 143 deletions

View file

@ -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();

View file

@ -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(

View file

@ -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();
}
}
}