From 996adeadb027c10905beb4fbc0f4911f9ac4f31c Mon Sep 17 00:00:00 2001 From: Henk Vergonet Date: Mon, 3 Jun 2024 13:14:22 +0200 Subject: [PATCH 1/4] rect: enhanced rounding chamfer options Introduces the extra "cswap" parameter that moves the chamfer and rounding center points. In the original code the negative roudings and chamfers are only extended in the x direction, this allows control over both directions. Example: $fn = 32; translate([0,0,-.51]) distribute(spacing=1.5) { // rounded examples: // original rect(rounding=.25*[1,1,-1,-1]); // cswap curve for quadrants [-1,1] and [-1,-1] rect(rounding=.25*[1,1,-1,-1], cswap = [0,1,1,0]); // chamfered examples: // original rect(chamfer=.25*[1,1,-1,-1]); // cswap chamfer for quadrants [-1,1] and [-1,-1] // note only affects the negative chamfers rect(chamfer=.25*[1,1,-1,-1], cswap = [0,1,1,0]); } Signed-off-by: Henk Vergonet --- shapes2d.scad | 304 +++++--------------------------------------------- 1 file changed, 25 insertions(+), 279 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 9e2a2ee..add1ff0 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -130,7 +130,7 @@ module square(size=1, center, anchor, spin) { // path = rect([40,30], chamfer=5, anchor=FRONT, spin=30); // stroke(path, closed=true); // move_copies(path) color("blue") circle(d=2,$fn=8); -module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0) { +module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, cswap = [0,0,0,0]) { errchk = assert(in_list(atype, ["box", "perim"])); size = [for (c = force_list(size,2)) max(0,c)]; if (!all_positive(size)) { @@ -144,7 +144,7 @@ module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0) { children(); } } else { - pts_over = rect(size=size, rounding=rounding, chamfer=chamfer, atype=atype, _return_override=true); + pts_over = rect(size=size, rounding=rounding, chamfer=chamfer, atype=atype, cswap = cswap, _return_override=true); pts = pts_over[0]; override = pts_over[1]; attachable(anchor, spin, two_d=true, size=size,override=override) { @@ -154,9 +154,7 @@ module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0) { } } - - -function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override) = +function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override, cswap = [0,0,0,0]) = assert(is_num(size) || is_vector(size,2)) assert(is_num(chamfer) || is_vector(chamfer,4)) assert(is_num(rounding) || is_vector(rounding,4)) @@ -200,13 +198,14 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, qchamf = chamfer[quad], qround = rounding[quad], cverts = quant(segs(abs(qinset)),4)/4, - step = 90/cverts, - cp = v_mul(size/2-[qinset,abs(qinset)], qpos), + step = 90/cverts, + cp = v_mul(size/2 + (cswap[quad] ? (qinset > 0 ? 0 : 1) : -1)*[qinset,abs(qinset)], qpos), + qpts = abs(qchamf) >= eps? [[0,abs(qinset)], [qinset,0]] : abs(qround) >= eps? [for (j=[0:1:cverts]) let(a=90-j*step) v_mul(polar_to_xy(abs(qinset),a),[sign(qinset),1])] : [[0,0]], - qfpts = [for (p=qpts) v_mul(p,qpos)], - qrpts = qpos.x*qpos.y < 0? reverse(qfpts) : qfpts, + qfpts = [for (p=qpts) v_mul(p,cswap[quad] ? -qpos : qpos)], + qrpts = (cswap[quad] && qinset > 0 ? -1 : 1) * qpos.x*qpos.y < 0? reverse(qfpts) : qfpts, cornerpt = atype=="box" || (qround==0 && qchamf==0) ? undef : qround<0 || qchamf<0 ? [[0,-qpos.y*min(qround,qchamf)]] : [for(seg=pair(qrpts)) let(isect=line_intersection(seg, [[0,0],qpos],SEGMENT,LINE)) if (is_def(isect) && isect!=seg[0]) isect] @@ -214,10 +213,11 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, assert(is_undef(cornerpt) || len(cornerpt)==1,"Cannot find corner point to anchor") [move(cp, p=qrpts), is_undef(cornerpt)? undef : move(cp,p=cornerpt[0])] ], - path = deduplicate(flatten(column(corners,0)),closed=true), + path = flatten(column(corners,0)), override = [for(i=[0:3]) let(quad=quadorder[i]) if (is_def(corners[i][1])) [quadpos[quad], [corners[i][1], min(chamfer[quad],rounding[quad])<0 ? [quadpos[quad].x,0] : undef]]] + ) _return_override ? [reorient(anchor,spin, two_d=true, size=size, p=path, override=override), override] : reorient(anchor,spin, two_d=true, size=size, p=path, override=override); @@ -549,7 +549,7 @@ function ellipse(r, d, realign=false, circum=false, uniform=false, anchor=CENTER // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards. // "side0", "side1", etc. = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -691,7 +691,7 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "tip0" ... "tip4" = Each tip has an anchor, pointing outwards. // "side0" ... "side4" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -752,7 +752,7 @@ module pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "tip0" ... "tip5" = Each tip has an anchor, pointing outwards. // "side0" ... "side5" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -812,7 +812,7 @@ module hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "tip0" ... "tip7" = Each tip has an anchor, pointing outwards. // "side0" ... "side7" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -863,8 +863,8 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // --- // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: -// "hypot" = Center of angled side, perpendicular to that side. +// Extra Anchors: +// hypot = Center of angled side, perpendicular to that side. // Example(2D): // right_triangle([40,30]); // Example(2D): With `center=true` @@ -1144,7 +1144,7 @@ module trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, ancho // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // atype = Choose "hull" or "intersect" anchor methods. Default: "hull" -// Named Anchors: +// Extra Anchors: // "tip0" ... "tip4" = Each tip has an anchor, pointing outwards. // "pit0" ... "pit4" = The inside corner between each tip has an anchor, pointing outwards. // "midpt0" ... "midpt4" = The center-point between each pair of tips has an anchor, pointing outwards. @@ -1318,7 +1318,7 @@ module jittered_poly(path, dist=1/512) { // Synopsis: Creates a 2D teardrop shape. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: teardrop(), onion(), keyhole() +// See Also: teardrop(), onion() // Description: // When called as a module, makes a 2D teardrop shape. Useful for extruding into 3D printable holes as it limits overhang to 45 degrees. Uses "intersect" style anchoring. // The cap_h parameter truncates the top of the teardrop. If cap_h is taller than the untruncated form then @@ -1418,7 +1418,7 @@ function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CEN // Synopsis: Creates an egg-shaped 2d object. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: circle(), ellipse(), glued_circles(), keyhole() +// See Also: circle(), ellipse(), glued_circles() // Usage: As Module // egg(length, r1|d1=, r2|d2=, R|D=) [ATTACHMENTS]; // Usage: As Function @@ -1439,7 +1439,7 @@ function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CEN // d1 = diameter of the left-hand circle // d2 = diameter of the right-hand circle // D = diameter of the joining arcs -// Named Anchors: +// Extra Anchors: // "left" = center of the left circle // "right" = center of the right circle // Example(2D,NoAxes): This first example shows how the egg is constructed from two circles and two joining arcs. @@ -1503,185 +1503,12 @@ module egg(length,r1,r2,R,d1,d2,D,anchor=CENTER, spin=0) } -// Function&Module: ring() -// Synopsis: Draws a 2D ring or partial ring or returns a region or path -// SynTags: Geom, Region, Path -// Topics: Shapes (2D), Paths (2D), Path Generators, Regions, Attachable -// See Also: arc(), circle() -// -// Usage: ring or partial ring from radii/diameters -// region=ring(n, r1=|d1=, r2=|d2=, [full=], [angle=], [start=]); -// Usage: ring or partial ring from radius and ring width -// region=ring(n, ring_width, r=|d=, [full=], [angle=], [start=]); -// Usage: ring or partial ring passing through three points -// region=ring(n, [ring_width], [r=,d=], points=[P0,P1,P2], [full=]); -// Usage: ring or partial ring from tangent point on segment `[P0,P1]` to the tangent point on segment `[P1,P2]`. -// region=ring(n, [ring_width], corner=[P0,P1,P2], [r=,d=], [r1|d1=], [r2=|d2=], [full=]); -// Usage: ring or partial ring based on setting a width at the X axis and height above the X axis -// region=ring(n, [ring_width], [r=|d=], width=, thickness=, [full=]); -// Usage: as a module -// ring(...) [ATTACHMENTS]; -// Description: -// If called as a function returns a region or path for a ring or part of a ring. If called as a module, creates the corresponding 2D ring or partial ring shape. -// The geometry of the ring can be specified using any of the methods supported by {{arc()}}. If `full` is true (the default) the ring will be complete and the -// returned value a region. If `full` is false then the return is a path describing a partial ring. The returned path is always clockwise with the larger radius arc first. -// A ring has two radii, the inner and outer. When specifying geometry you must somehow specify one radius, which can be directly with `r=` or `r1=` or by giving a point list with -// or without a center point. You specify the second radius by giving `r=` directly, or `r2=` if you used `r1=` for the first radius, or by giving `ring_width`. If `ring_width` -// the second radius will be larger than the first; if `ring_width` is negative the second radius will be smaller. -// Arguments: -// n = Number of vertices to use for the inner and outer portions of the ring -// ring_width = width of the ring. Can be positive or negative -// --- -// r1/d1 = inner radius or diameter of the ring -// r2/d2 = outer radius or diameter of the ring -// r/d = second radius or diameter of ring when r1 or d1 are not given -// full = if true create a full ring, if false create a partial ring. Default: true unless `angle` is given -// cp = Centerpoint of ring. -// points = Points on the ring boundary. -// corner = A path of two segments to fit the ring 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 -// width = If given with `thickness`, ring is defined based on an arc with ends on X axis. -// thickness = If given with `width`, ring is defined based on an arc with ends on X axis, and this height above the X axis. -// start = Start angle of ring. Default: 0 -// angle = If scalar, the end angle in degrees relative to start parameter. If a vector specifies start and end angles of ring. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). (Module only) Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). (Module only) Default: `0` -// Examples(2D): -// ring(r1=5,r2=7, n=32); -// ring(r=5,ring_width=-1, n=32); -// ring(r=7, n=5, ring_width=-4); -// ring(points=[[0,0],[3,3],[5,2]], ring_width=2, n=32); -// ring(points=[[0,0],[3,3],[5,2]], r=1, n=32); -// ring(cp=[3,3], points=[[4,4],[1,3]], ring_width=1); -// ring(corner=[[0,0],[4,4],[7,3]], r2=2, r1=1.5,n=22,full=false); -// ring(r1=5,r2=7, angle=[33,110], n=32); -// ring(r1=5,r2=7, angle=[0,360], n=32); // full circle -// ring(r=5, points=[[0,0],[3,3],[5,2]], full=false, n=32); -// ring(32,-2, cp=[1,1], points=[[4,4],[-3,6]], full=false); -// ring(r=5,ring_width=-1, n=32); -// ring(points=[[0,0],[3,3],[5,2]], ring_width=2, n=32); -// ring(points=[[0,0],[3,3],[5,2]], r=1, n=32); -// ring(cp=[3,3], points=[[4,4],[1,3]], ring_width=1); -// Example(2D): Using corner, the outer radius is the one tangent to the corner -// corner = [[0,0],[4,4],[7,3]]; -// ring(corner=corner, r2=3, r1=2,n=22); -// stroke(corner, width=.1,color="red"); -// Example(2D): For inner radius tangent to a corner, specify `r=` and `ring_width`. -// corner = [[0,0],[4,4],[7,3]]; -// ring(corner=corner, r=3, ring_width=1,n=22,full=false); -// stroke(corner, width=.1,color="red"); -// Example(2D): -// $fn=128; -// region = ring(width=5,thickness=1.5,ring_width=2); -// path = ring(width=5,thickness=1.5,ring_width=2,full=false); -// stroke(region,width=.25); -// color("red") dashed_stroke(path,dashpat=[1.5,1.5],closed=true,width=.25); - -module ring(n,ring_width,r,r1,r2,angle,d,d1,d2,cp,points,corner, width,thickness,start, long=false, full=true, cw=false,ccw=false, anchor=CENTER, spin=0) -{ - R = ring(n=n,r=r,ring_width=ring_width,r1=r1,r2=r2,angle=angle,d=d,d1=d1,d2=d2,cp=cp,points=points,corner=corner, width=width,thickness=thickness,start=start, - long=long, full=full, cw=cw, ccw=ccw); - attachable(anchor,spin,two_d=true,region=is_region(R)?R:undef,path=is_region(R)?undef:R,extent=false) { - region(R); - children(); - } -} - -function ring(n,ring_width,r,r1,r2,angle,d,d1,d2,cp,points,corner, width,thickness,start, long=false, full=true, cw=false,ccw=false) = - let( - r1 = is_def(r1) ? assert(is_undef(d),"Cannot define r1 and d1")r1 - : is_def(d1) ? d1/2 - : undef, - r2 = is_def(r2) ? assert(is_undef(d),"Cannot define r2 and d2")r2 - : is_def(d2) ? d2/2 - : undef, - r = is_def(r) ? assert(is_undef(d),"Cannot define r and d")r - : is_def(d) ? d/2 - : undef, - full = is_def(angle) ? false : full - ) - assert(is_undef(start) || is_def(angle), "start requires angle") - assert(is_undef(angle) || !any_defined([thickness,width,points,corner]), "Cannot give angle with points, corner, width or thickness") - assert(!is_vector(angle,2) || abs(angle[1]-angle[0]) <= 360, "angle gives more than 360 degrees") - assert(is_undef(points) || is_path(points,2), str("Points must be a 2d vector",points)) - assert(!any_defined([points,thickness,width]) || num_defined([r1,r2])==0, "Cannot give r1, r2, d1, or d2 with points, width or thickness") - is_def(width) && is_def(thickness)? - assert(!any_defined([r,cp,points,angle,start]), "Conflicting or invalid parameters to ring") - assert(all_positive([width,thickness]), "Width and thickness must be positive") - ring(n=n,r=r,ring_width=ring_width,points=[[width/2,0], [0,thickness], [-width/2,0]],full=full) - : full && is_undef(cp) && is_def(points) ? - assert(is_def(points) && len(points)==3, "Without cp given, must provide exactly three points") - assert(num_defined([r,ring_width]), "Must give r or ring_width with point list") - let( - ctr_rad = circle_3points(points), - dummy=assert(is_def(ctr_rad[0]), "Collinear points given to ring()"), - part1 = move(ctr_rad[0],circle(r=ctr_rad[1], $fn=is_def(n) ? n : $fn)), - first_r = norm(part1[0]-ctr_rad[0]), - r = is_def(r) ? r : first_r+ring_width, - part2 = move(ctr_rad[0],circle(r=r, $fn=is_def(n) ? n : $fn)) - ) - assert(first_r!=r, "Ring has zero width") - (first_r>r ? [part1, reverse(part2)] : [part2, reverse(part1)]) - : full && is_def(corner) ? - assert(is_path(corner,2) && len(corner)==3, "corner must be a list of 3 points") - assert(!any_defined([thickness,width,points,cp,angle.start]), "Conflicting or invalid parameters to ring") - let(parmok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) - || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width))) - assert(parmok, "With corner must give (r1 and r2) or (r and ring_width), but you gave some other combination") - let( - newr1 = is_def(r1) ? min(r1,r2) : min(r,r+ring_width), - newr2 = is_def(r2) ? max(r2,r1) : max(r,r+ring_width), - data = circle_2tangents(newr2,corner[0],corner[1],corner[2]), - cp=data[0] - ) - [move(cp,circle($fn=is_def(n) ? n : $fn, r=newr2)),move(cp, circle( $fn=is_def(n) ? n : $fn, r=newr1))] - : full && is_def(cp) && is_def(points) ? - assert(in_list(len(points),[1,2]), "With cp must give a list of one or two points.") - assert(num_defined([r,ring_width]), "Must give r or ring_width with point list") - let( - first_r=norm(points[0]-cp), - part1 = move(cp,circle(r=first_r, $fn=is_def(n) ? n : $fn)), - r = is_def(r) ? r : first_r+ring_width, - part2 = move(cp,circle(r=r, $fn=is_def(n) ? n : $fn)) - ) - assert(first_r!=r, "Ring has zero width") - first_r>r ? [part1, reverse(part2)] : [part2, reverse(part1)] - : full || angle==360 || (is_vector(angle,2) && abs(angle[1]-angle[0])==360) ? - let(parmok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) - || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width))) - assert(parmok, "Must give (r1 and r2) or (r and ring_width), but you gave some other combination") - let( - newr1 = is_def(r1) ? min(r1,r2) : min(r,r+ring_width), - newr2 = is_def(r2) ? max(r2,r1) : max(r,r+ring_width), - cp = default(cp,[0,0]) - ) - [move(cp,circle($fn=is_def(n) ? n : $fn, r=newr2)),move(cp, circle( $fn=is_def(n) ? n : $fn, r=newr1))] - : let( - parmRok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) - || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width)), - pass_r = any_defined([points,thickness]) ? assert(!any_defined([r1,r2]),"Cannot give r1, d1, r2, or d2 with a point list or width & thickness") - assert(num_defined([ring_width,r])==1, "Must defined exactly one of r and ring_width when using a pointlist or width & thickness") - undef - : assert(num_defined([r,r2])==1,"Cannot give r or d and r1 or d1") first_defined([r,r2]), - base_arc = clockwise_polygon(arc(r=pass_r,n=n,angle=angle,cp=cp,points=points, corner=corner, width=width, thickness=thickness,start=start, long=long, cw=cw,ccw=ccw,wedge=true)), - center = base_arc[0], - arc1 = list_tail(base_arc,1), - r_actual = norm(center-arc1[0]), - new_r = is_def(ring_width) ? r_actual+ring_width - : first_defined([r,r1]), - pts = [center+new_r*unit(arc1[0]-center), center+new_r*unit(arc1[floor(len(arc1)/2)]-center), center+new_r*unit(last(arc1)-center)], - second=arc(n=n,points=pts), - arc2 = is_polygon_clockwise(second) ? second : reverse(second) - ) new_r>r_actual ? concat(arc2, reverse(arc1)) : concat(arc1,reverse(arc2)); - // Function&Module: glued_circles() // Synopsis: Creates a shape of two circles joined by a curved waist. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: circle(), ellipse(), egg(), keyhole() +// See Also: circle(), ellipse(), egg() // Usage: As Module // glued_circles(r/d=, [spread], [tangent], ...) [ATTACHMENTS]; // Usage: As Function @@ -1743,87 +1570,9 @@ module glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) { } -// Function&Module: keyhole() -// Synopsis: Creates a 2D keyhole shape. -// SynTags: Geom, Path -// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: circle(), ellipse(), egg(), glued_circles() -// Usage: As Module -// keyhole(l/length=, r1/d1=, r2/d2=, [shoulder_r=], ...) [ATTACHMENTS]; -// Usage: As Function -// path = keyhole(l/length=, r1/d1=, r2/d2=, [shoulder_r=], ...); -// Description: -// When called as a function, returns a 2D path forming a shape of two differently sized circles joined by a straight slot, making what looks like a keyhole. -// When called as a module, creates a 2D shape of two differently sized circles joined by a straight slot, making what looks like a keyhole. Uses "hull" style anchoring. -// Arguments: -// l = The distance between the centers of the two circles. Default: `15` -// r1= The radius of the back circle, centered on `[0,0]`. Default: `2.5` -// r2= The radius of the forward circle, centered on `[0,-length]`. Default: `5` -// --- -// shoulder_r = The radius of the rounding of the shoulder between the larger circle, and the slot that leads to the smaller circle. Default: `0` -// d1= The diameter of the back circle, centered on `[0,0]`. -// d2= The diameter of the forward circle, centered on `[0,-l]`. -// length = An alternate name for the `l=` argument. -// anchor = Translate so anchor point is at origin (0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` -// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Examples(2D): -// keyhole(40, 10, 30); -// keyhole(l=60, r1=20, r2=40); -// Example(2D): Making the forward circle larger than the back circle -// keyhole(l=60, r1=40, r2=20); -// Example(2D): Centering on the larger hole: -// keyhole(l=60, r1=40, r2=20, spin=180); -// Example(2D): Rounding the shoulders -// keyhole(l=60, r1=20, r2=40, shoulder_r=20); -// Example(2D): Called as Function -// stroke(closed=true, keyhole(l=60, r1=20, r2=40)); - -function keyhole(l, r1, r2, shoulder_r=0, d1, d2, length, anchor=CTR, spin=0) = - let( - l = first_defined([l,length,15]), - r1 = get_radius(r=r1, d=d1, dflt=5), - r2 = get_radius(r=r2, d=d2, dflt=10) - ) - assert(is_num(l) && l>0) - assert(l>=max(r1,r2)) - assert(is_undef(shoulder_r) || (is_num(shoulder_r) && shoulder_r>=0)) - let( - cp1 = [0,0], - cp2 = cp1 + [0,-l], - shoulder_r = is_num(shoulder_r)? shoulder_r : min(r1,r2) / 2, - minr = min(r1, r2) + shoulder_r, - maxr = max(r1, r2) + shoulder_r, - dy = opp_hyp_to_adj(minr, maxr), - spt1 = r1>r2? cp1+[minr,-dy] : cp2+[minr,dy], - spt2 = [-spt1.x, spt1.y], - ds = spt1 - (r1>r2? cp1 : cp2), - ang = atan2(abs(ds.y), abs(ds.x)), - path = r1>r2? [ - if (shoulder_r<=0) spt1 - else each arc(r=shoulder_r, cp=spt1, start=180-ang, angle=ang, endpoint=false), - each arc(r=r2, cp=cp2, start=0, angle=-180, endpoint=false), - if (shoulder_r<=0) spt2 - else each arc(r=shoulder_r, cp=spt2, start=0, angle=ang, endpoint=false), - each arc(r=r1, cp=cp1, start=180+ang, angle=-180-2*ang, endpoint=false), - ] : [ - if (shoulder_r<=0) spt1 - else each arc(r=shoulder_r, cp=spt1, start=180, angle=ang, endpoint=false), - each arc(r=r2, cp=cp2, start=ang, angle=-180-2*ang, endpoint=false), - if (shoulder_r<=0) spt2 - else each arc(r=shoulder_r, cp=spt2, start=360-ang, angle=ang, endpoint=false), - each arc(r=r1, cp=cp1, start=180, angle=-180, endpoint=false), - ] - ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); - - -module keyhole(l, r1, r2, shoulder_r=0, d1, d2, length, anchor=CTR, spin=0) { - path = keyhole(l=l, r1=r1, r2=r2, shoulder_r=shoulder_r, d1=d1, d2=d2, length=length); - attachable(anchor,spin, two_d=true, path=path, extent=true) { - polygon(path); - children(); - } -} +function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = + pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1); // Function&Module: supershape() // Synopsis: Creates a 2D [Superformula](https://en.wikipedia.org/wiki/Superformula) shape. @@ -1907,9 +1656,6 @@ module supershape(step=0.5,n,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=u } } -function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = - pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1); - // Function&Module: reuleaux_polygon() // Synopsis: Creates a constant-width shape that is not circular. @@ -1930,7 +1676,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = // d = Diameter of the shape. Scale shape to fit in a circle of diameter d. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards. // Examples(2D): // reuleaux_polygon(n=3, r=50); @@ -2025,7 +1771,7 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // script = The script the text is in. Default: `"latin"` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"baseline"` // spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0` -// Named Anchors: +// Extra Anchors: // "baseline" = Anchors at the baseline of the text, at the start of the string. // str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector. // Examples(2D): From 7cc14305579dc6761c59855704f4747327fb1a65 Mon Sep 17 00:00:00 2001 From: Henk Vergonet Date: Mon, 3 Jun 2024 13:35:20 +0200 Subject: [PATCH 2/4] rebase with latest head --- shapes2d.scad | 287 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 270 insertions(+), 17 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index add1ff0..b159405 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -213,11 +213,10 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, assert(is_undef(cornerpt) || len(cornerpt)==1,"Cannot find corner point to anchor") [move(cp, p=qrpts), is_undef(cornerpt)? undef : move(cp,p=cornerpt[0])] ], - path = flatten(column(corners,0)), + path = deduplicate(flatten(column(corners,0)),closed=true), override = [for(i=[0:3]) let(quad=quadorder[i]) if (is_def(corners[i][1])) [quadpos[quad], [corners[i][1], min(chamfer[quad],rounding[quad])<0 ? [quadpos[quad].x,0] : undef]]] - ) _return_override ? [reorient(anchor,spin, two_d=true, size=size, p=path, override=override), override] : reorient(anchor,spin, two_d=true, size=size, p=path, override=override); @@ -549,7 +548,7 @@ function ellipse(r, d, realign=false, circum=false, uniform=false, anchor=CENTER // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards. // "side0", "side1", etc. = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -691,7 +690,7 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "tip0" ... "tip4" = Each tip has an anchor, pointing outwards. // "side0" ... "side4" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -752,7 +751,7 @@ module pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "tip0" ... "tip5" = Each tip has an anchor, pointing outwards. // "side0" ... "side5" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -812,7 +811,7 @@ module hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "tip0" ... "tip7" = Each tip has an anchor, pointing outwards. // "side0" ... "side7" = The center of each side has an anchor, pointing outwards. // Example(2D): by Outer Size @@ -863,8 +862,8 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // --- // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: -// hypot = Center of angled side, perpendicular to that side. +// Named Anchors: +// "hypot" = Center of angled side, perpendicular to that side. // Example(2D): // right_triangle([40,30]); // Example(2D): With `center=true` @@ -1144,7 +1143,7 @@ module trapezoid(h, w1, w2, ang, shift, chamfer=0, rounding=0, flip=false, ancho // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // atype = Choose "hull" or "intersect" anchor methods. Default: "hull" -// Extra Anchors: +// Named Anchors: // "tip0" ... "tip4" = Each tip has an anchor, pointing outwards. // "pit0" ... "pit4" = The inside corner between each tip has an anchor, pointing outwards. // "midpt0" ... "midpt4" = The center-point between each pair of tips has an anchor, pointing outwards. @@ -1318,7 +1317,7 @@ module jittered_poly(path, dist=1/512) { // Synopsis: Creates a 2D teardrop shape. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: teardrop(), onion() +// See Also: teardrop(), onion(), keyhole() // Description: // When called as a module, makes a 2D teardrop shape. Useful for extruding into 3D printable holes as it limits overhang to 45 degrees. Uses "intersect" style anchoring. // The cap_h parameter truncates the top of the teardrop. If cap_h is taller than the untruncated form then @@ -1418,7 +1417,7 @@ function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CEN // Synopsis: Creates an egg-shaped 2d object. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: circle(), ellipse(), glued_circles() +// See Also: circle(), ellipse(), glued_circles(), keyhole() // Usage: As Module // egg(length, r1|d1=, r2|d2=, R|D=) [ATTACHMENTS]; // Usage: As Function @@ -1439,7 +1438,7 @@ function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CEN // d1 = diameter of the left-hand circle // d2 = diameter of the right-hand circle // D = diameter of the joining arcs -// Extra Anchors: +// Named Anchors: // "left" = center of the left circle // "right" = center of the right circle // Example(2D,NoAxes): This first example shows how the egg is constructed from two circles and two joining arcs. @@ -1503,12 +1502,185 @@ module egg(length,r1,r2,R,d1,d2,D,anchor=CENTER, spin=0) } +// Function&Module: ring() +// Synopsis: Draws a 2D ring or partial ring or returns a region or path +// SynTags: Geom, Region, Path +// Topics: Shapes (2D), Paths (2D), Path Generators, Regions, Attachable +// See Also: arc(), circle() +// +// Usage: ring or partial ring from radii/diameters +// region=ring(n, r1=|d1=, r2=|d2=, [full=], [angle=], [start=]); +// Usage: ring or partial ring from radius and ring width +// region=ring(n, ring_width, r=|d=, [full=], [angle=], [start=]); +// Usage: ring or partial ring passing through three points +// region=ring(n, [ring_width], [r=,d=], points=[P0,P1,P2], [full=]); +// Usage: ring or partial ring from tangent point on segment `[P0,P1]` to the tangent point on segment `[P1,P2]`. +// region=ring(n, [ring_width], corner=[P0,P1,P2], [r=,d=], [r1|d1=], [r2=|d2=], [full=]); +// Usage: ring or partial ring based on setting a width at the X axis and height above the X axis +// region=ring(n, [ring_width], [r=|d=], width=, thickness=, [full=]); +// Usage: as a module +// ring(...) [ATTACHMENTS]; +// Description: +// If called as a function returns a region or path for a ring or part of a ring. If called as a module, creates the corresponding 2D ring or partial ring shape. +// The geometry of the ring can be specified using any of the methods supported by {{arc()}}. If `full` is true (the default) the ring will be complete and the +// returned value a region. If `full` is false then the return is a path describing a partial ring. The returned path is always clockwise with the larger radius arc first. +// A ring has two radii, the inner and outer. When specifying geometry you must somehow specify one radius, which can be directly with `r=` or `r1=` or by giving a point list with +// or without a center point. You specify the second radius by giving `r=` directly, or `r2=` if you used `r1=` for the first radius, or by giving `ring_width`. If `ring_width` +// the second radius will be larger than the first; if `ring_width` is negative the second radius will be smaller. +// Arguments: +// n = Number of vertices to use for the inner and outer portions of the ring +// ring_width = width of the ring. Can be positive or negative +// --- +// r1/d1 = inner radius or diameter of the ring +// r2/d2 = outer radius or diameter of the ring +// r/d = second radius or diameter of ring when r1 or d1 are not given +// full = if true create a full ring, if false create a partial ring. Default: true unless `angle` is given +// cp = Centerpoint of ring. +// points = Points on the ring boundary. +// corner = A path of two segments to fit the ring 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 +// width = If given with `thickness`, ring is defined based on an arc with ends on X axis. +// thickness = If given with `width`, ring is defined based on an arc with ends on X axis, and this height above the X axis. +// start = Start angle of ring. Default: 0 +// angle = If scalar, the end angle in degrees relative to start parameter. If a vector specifies start and end angles of ring. +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). (Module only) Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). (Module only) Default: `0` +// Examples(2D): +// ring(r1=5,r2=7, n=32); +// ring(r=5,ring_width=-1, n=32); +// ring(r=7, n=5, ring_width=-4); +// ring(points=[[0,0],[3,3],[5,2]], ring_width=2, n=32); +// ring(points=[[0,0],[3,3],[5,2]], r=1, n=32); +// ring(cp=[3,3], points=[[4,4],[1,3]], ring_width=1); +// ring(corner=[[0,0],[4,4],[7,3]], r2=2, r1=1.5,n=22,full=false); +// ring(r1=5,r2=7, angle=[33,110], n=32); +// ring(r1=5,r2=7, angle=[0,360], n=32); // full circle +// ring(r=5, points=[[0,0],[3,3],[5,2]], full=false, n=32); +// ring(32,-2, cp=[1,1], points=[[4,4],[-3,6]], full=false); +// ring(r=5,ring_width=-1, n=32); +// ring(points=[[0,0],[3,3],[5,2]], ring_width=2, n=32); +// ring(points=[[0,0],[3,3],[5,2]], r=1, n=32); +// ring(cp=[3,3], points=[[4,4],[1,3]], ring_width=1); +// Example(2D): Using corner, the outer radius is the one tangent to the corner +// corner = [[0,0],[4,4],[7,3]]; +// ring(corner=corner, r2=3, r1=2,n=22); +// stroke(corner, width=.1,color="red"); +// Example(2D): For inner radius tangent to a corner, specify `r=` and `ring_width`. +// corner = [[0,0],[4,4],[7,3]]; +// ring(corner=corner, r=3, ring_width=1,n=22,full=false); +// stroke(corner, width=.1,color="red"); +// Example(2D): +// $fn=128; +// region = ring(width=5,thickness=1.5,ring_width=2); +// path = ring(width=5,thickness=1.5,ring_width=2,full=false); +// stroke(region,width=.25); +// color("red") dashed_stroke(path,dashpat=[1.5,1.5],closed=true,width=.25); + +module ring(n,ring_width,r,r1,r2,angle,d,d1,d2,cp,points,corner, width,thickness,start, long=false, full=true, cw=false,ccw=false, anchor=CENTER, spin=0) +{ + R = ring(n=n,r=r,ring_width=ring_width,r1=r1,r2=r2,angle=angle,d=d,d1=d1,d2=d2,cp=cp,points=points,corner=corner, width=width,thickness=thickness,start=start, + long=long, full=full, cw=cw, ccw=ccw); + attachable(anchor,spin,two_d=true,region=is_region(R)?R:undef,path=is_region(R)?undef:R,extent=false) { + region(R); + children(); + } +} + +function ring(n,ring_width,r,r1,r2,angle,d,d1,d2,cp,points,corner, width,thickness,start, long=false, full=true, cw=false,ccw=false) = + let( + r1 = is_def(r1) ? assert(is_undef(d),"Cannot define r1 and d1")r1 + : is_def(d1) ? d1/2 + : undef, + r2 = is_def(r2) ? assert(is_undef(d),"Cannot define r2 and d2")r2 + : is_def(d2) ? d2/2 + : undef, + r = is_def(r) ? assert(is_undef(d),"Cannot define r and d")r + : is_def(d) ? d/2 + : undef, + full = is_def(angle) ? false : full + ) + assert(is_undef(start) || is_def(angle), "start requires angle") + assert(is_undef(angle) || !any_defined([thickness,width,points,corner]), "Cannot give angle with points, corner, width or thickness") + assert(!is_vector(angle,2) || abs(angle[1]-angle[0]) <= 360, "angle gives more than 360 degrees") + assert(is_undef(points) || is_path(points,2), str("Points must be a 2d vector",points)) + assert(!any_defined([points,thickness,width]) || num_defined([r1,r2])==0, "Cannot give r1, r2, d1, or d2 with points, width or thickness") + is_def(width) && is_def(thickness)? + assert(!any_defined([r,cp,points,angle,start]), "Conflicting or invalid parameters to ring") + assert(all_positive([width,thickness]), "Width and thickness must be positive") + ring(n=n,r=r,ring_width=ring_width,points=[[width/2,0], [0,thickness], [-width/2,0]],full=full) + : full && is_undef(cp) && is_def(points) ? + assert(is_def(points) && len(points)==3, "Without cp given, must provide exactly three points") + assert(num_defined([r,ring_width]), "Must give r or ring_width with point list") + let( + ctr_rad = circle_3points(points), + dummy=assert(is_def(ctr_rad[0]), "Collinear points given to ring()"), + part1 = move(ctr_rad[0],circle(r=ctr_rad[1], $fn=is_def(n) ? n : $fn)), + first_r = norm(part1[0]-ctr_rad[0]), + r = is_def(r) ? r : first_r+ring_width, + part2 = move(ctr_rad[0],circle(r=r, $fn=is_def(n) ? n : $fn)) + ) + assert(first_r!=r, "Ring has zero width") + (first_r>r ? [part1, reverse(part2)] : [part2, reverse(part1)]) + : full && is_def(corner) ? + assert(is_path(corner,2) && len(corner)==3, "corner must be a list of 3 points") + assert(!any_defined([thickness,width,points,cp,angle.start]), "Conflicting or invalid parameters to ring") + let(parmok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) + || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width))) + assert(parmok, "With corner must give (r1 and r2) or (r and ring_width), but you gave some other combination") + let( + newr1 = is_def(r1) ? min(r1,r2) : min(r,r+ring_width), + newr2 = is_def(r2) ? max(r2,r1) : max(r,r+ring_width), + data = circle_2tangents(newr2,corner[0],corner[1],corner[2]), + cp=data[0] + ) + [move(cp,circle($fn=is_def(n) ? n : $fn, r=newr2)),move(cp, circle( $fn=is_def(n) ? n : $fn, r=newr1))] + : full && is_def(cp) && is_def(points) ? + assert(in_list(len(points),[1,2]), "With cp must give a list of one or two points.") + assert(num_defined([r,ring_width]), "Must give r or ring_width with point list") + let( + first_r=norm(points[0]-cp), + part1 = move(cp,circle(r=first_r, $fn=is_def(n) ? n : $fn)), + r = is_def(r) ? r : first_r+ring_width, + part2 = move(cp,circle(r=r, $fn=is_def(n) ? n : $fn)) + ) + assert(first_r!=r, "Ring has zero width") + first_r>r ? [part1, reverse(part2)] : [part2, reverse(part1)] + : full || angle==360 || (is_vector(angle,2) && abs(angle[1]-angle[0])==360) ? + let(parmok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) + || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width))) + assert(parmok, "Must give (r1 and r2) or (r and ring_width), but you gave some other combination") + let( + newr1 = is_def(r1) ? min(r1,r2) : min(r,r+ring_width), + newr2 = is_def(r2) ? max(r2,r1) : max(r,r+ring_width), + cp = default(cp,[0,0]) + ) + [move(cp,circle($fn=is_def(n) ? n : $fn, r=newr2)),move(cp, circle( $fn=is_def(n) ? n : $fn, r=newr1))] + : let( + parmRok = (all_positive([r1,r2]) && num_defined([r,ring_width])==0) + || (num_defined([r1,r2])==0 && all_positive([r]) && is_finite(ring_width)), + pass_r = any_defined([points,thickness]) ? assert(!any_defined([r1,r2]),"Cannot give r1, d1, r2, or d2 with a point list or width & thickness") + assert(num_defined([ring_width,r])==1, "Must defined exactly one of r and ring_width when using a pointlist or width & thickness") + undef + : assert(num_defined([r,r2])==1,"Cannot give r or d and r1 or d1") first_defined([r,r2]), + base_arc = clockwise_polygon(arc(r=pass_r,n=n,angle=angle,cp=cp,points=points, corner=corner, width=width, thickness=thickness,start=start, long=long, cw=cw,ccw=ccw,wedge=true)), + center = base_arc[0], + arc1 = list_tail(base_arc,1), + r_actual = norm(center-arc1[0]), + new_r = is_def(ring_width) ? r_actual+ring_width + : first_defined([r,r1]), + pts = [center+new_r*unit(arc1[0]-center), center+new_r*unit(arc1[floor(len(arc1)/2)]-center), center+new_r*unit(last(arc1)-center)], + second=arc(n=n,points=pts), + arc2 = is_polygon_clockwise(second) ? second : reverse(second) + ) new_r>r_actual ? concat(arc2, reverse(arc1)) : concat(arc1,reverse(arc2)); + // Function&Module: glued_circles() // Synopsis: Creates a shape of two circles joined by a curved waist. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable -// See Also: circle(), ellipse(), egg() +// See Also: circle(), ellipse(), egg(), keyhole() // Usage: As Module // glued_circles(r/d=, [spread], [tangent], ...) [ATTACHMENTS]; // Usage: As Function @@ -1570,9 +1742,87 @@ module glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) { } +// Function&Module: keyhole() +// Synopsis: Creates a 2D keyhole shape. +// SynTags: Geom, Path +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable +// See Also: circle(), ellipse(), egg(), glued_circles() +// Usage: As Module +// keyhole(l/length=, r1/d1=, r2/d2=, [shoulder_r=], ...) [ATTACHMENTS]; +// Usage: As Function +// path = keyhole(l/length=, r1/d1=, r2/d2=, [shoulder_r=], ...); +// Description: +// When called as a function, returns a 2D path forming a shape of two differently sized circles joined by a straight slot, making what looks like a keyhole. +// When called as a module, creates a 2D shape of two differently sized circles joined by a straight slot, making what looks like a keyhole. Uses "hull" style anchoring. +// Arguments: +// l = The distance between the centers of the two circles. Default: `15` +// r1= The radius of the back circle, centered on `[0,0]`. Default: `2.5` +// r2= The radius of the forward circle, centered on `[0,-length]`. Default: `5` +// --- +// shoulder_r = The radius of the rounding of the shoulder between the larger circle, and the slot that leads to the smaller circle. Default: `0` +// d1= The diameter of the back circle, centered on `[0,0]`. +// d2= The diameter of the forward circle, centered on `[0,-l]`. +// length = An alternate name for the `l=` argument. +// anchor = Translate so anchor point is at origin (0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` +// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` +// Examples(2D): +// keyhole(40, 10, 30); +// keyhole(l=60, r1=20, r2=40); +// Example(2D): Making the forward circle larger than the back circle +// keyhole(l=60, r1=40, r2=20); +// Example(2D): Centering on the larger hole: +// keyhole(l=60, r1=40, r2=20, spin=180); +// Example(2D): Rounding the shoulders +// keyhole(l=60, r1=20, r2=40, shoulder_r=20); +// Example(2D): Called as Function +// stroke(closed=true, keyhole(l=60, r1=20, r2=40)); + +function keyhole(l, r1, r2, shoulder_r=0, d1, d2, length, anchor=CTR, spin=0) = + let( + l = first_defined([l,length,15]), + r1 = get_radius(r=r1, d=d1, dflt=5), + r2 = get_radius(r=r2, d=d2, dflt=10) + ) + assert(is_num(l) && l>0) + assert(l>=max(r1,r2)) + assert(is_undef(shoulder_r) || (is_num(shoulder_r) && shoulder_r>=0)) + let( + cp1 = [0,0], + cp2 = cp1 + [0,-l], + shoulder_r = is_num(shoulder_r)? shoulder_r : min(r1,r2) / 2, + minr = min(r1, r2) + shoulder_r, + maxr = max(r1, r2) + shoulder_r, + dy = opp_hyp_to_adj(minr, maxr), + spt1 = r1>r2? cp1+[minr,-dy] : cp2+[minr,dy], + spt2 = [-spt1.x, spt1.y], + ds = spt1 - (r1>r2? cp1 : cp2), + ang = atan2(abs(ds.y), abs(ds.x)), + path = r1>r2? [ + if (shoulder_r<=0) spt1 + else each arc(r=shoulder_r, cp=spt1, start=180-ang, angle=ang, endpoint=false), + each arc(r=r2, cp=cp2, start=0, angle=-180, endpoint=false), + if (shoulder_r<=0) spt2 + else each arc(r=shoulder_r, cp=spt2, start=0, angle=ang, endpoint=false), + each arc(r=r1, cp=cp1, start=180+ang, angle=-180-2*ang, endpoint=false), + ] : [ + if (shoulder_r<=0) spt1 + else each arc(r=shoulder_r, cp=spt1, start=180, angle=ang, endpoint=false), + each arc(r=r2, cp=cp2, start=ang, angle=-180-2*ang, endpoint=false), + if (shoulder_r<=0) spt2 + else each arc(r=shoulder_r, cp=spt2, start=360-ang, angle=ang, endpoint=false), + each arc(r=r1, cp=cp1, start=180, angle=-180, endpoint=false), + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); + + +module keyhole(l, r1, r2, shoulder_r=0, d1, d2, length, anchor=CTR, spin=0) { + path = keyhole(l=l, r1=r1, r2=r2, shoulder_r=shoulder_r, d1=d1, d2=d2, length=length); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } +} -function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = - pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1); // Function&Module: supershape() // Synopsis: Creates a 2D [Superformula](https://en.wikipedia.org/wiki/Superformula) shape. @@ -1656,6 +1906,9 @@ module supershape(step=0.5,n,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=u } } +function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = + pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1); + // Function&Module: reuleaux_polygon() // Synopsis: Creates a constant-width shape that is not circular. @@ -1676,7 +1929,7 @@ module supershape(step=0.5,n,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=u // d = Diameter of the shape. Scale shape to fit in a circle of diameter d. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards. // Examples(2D): // reuleaux_polygon(n=3, r=50); @@ -1771,7 +2024,7 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // script = The script the text is in. Default: `"latin"` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"baseline"` // spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0` -// Extra Anchors: +// Named Anchors: // "baseline" = Anchors at the baseline of the text, at the start of the string. // str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector. // Examples(2D): From 5221b80c4a3faa284046512dc3f109e991aad844 Mon Sep 17 00:00:00 2001 From: Henk Vergonet Date: Mon, 3 Jun 2024 13:50:01 +0200 Subject: [PATCH 3/4] rect: whitespace fixes --- shapes2d.scad | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index b159405..fcf3e72 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -154,6 +154,8 @@ module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, c } } + + function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override, cswap = [0,0,0,0]) = assert(is_num(size) || is_vector(size,2)) assert(is_num(chamfer) || is_vector(chamfer,4)) @@ -198,7 +200,7 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, qchamf = chamfer[quad], qround = rounding[quad], cverts = quant(segs(abs(qinset)),4)/4, - step = 90/cverts, + step = 90/cverts, cp = v_mul(size/2 + (cswap[quad] ? (qinset > 0 ? 0 : 1) : -1)*[qinset,abs(qinset)], qpos), qpts = abs(qchamf) >= eps? [[0,abs(qinset)], [qinset,0]] : From b63554e6248eff559386a896cc9861d9379229f2 Mon Sep 17 00:00:00 2001 From: Henk Vergonet Date: Wed, 5 Jun 2024 18:08:27 +0200 Subject: [PATCH 4/4] rect: enhanced rounding chamfer options, fixes - Renamed cswap to corner_flip - corner_flip is boolean vector, and is automatically expanded from a scalar. - when atype = "perim", the anchors are now updated accordingly. corner_flip = Flips the direction of the rouding curve or roudover and chamfer spikes. If true it produces spikes on the Y axis. If false it produces spikes on the X axis. If given as a list of four booleans it flips the direction for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: false (no flip) Signed-off-by: Henk Vergonet --- shapes2d.scad | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index fcf3e72..981b5f1 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -94,6 +94,7 @@ module square(size=1, center, anchor, spin) { // --- // rounding = The rounding radius for the corners. If negative, produces external roundover spikes on the X axis. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding) // chamfer = The chamfer size for the corners. If negative, produces external chamfer spikes on the X axis. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer) +// corner_flip = Flips the direction of the rouding curve or roudover and chamfer spikes. If true it produces spikes on the Y axis. If false it produces spikes on the X axis. If given as a list of four booleans it flips the direction for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: false (no flip) // atype = The type of anchoring to use with `anchor=`. Valid opptions are "box" and "perim". This lets you choose between putting anchors on the rounded or chamfered perimeter, or on the square bounding box of the shape. Default: "box" // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` @@ -114,6 +115,8 @@ module square(size=1, center, anchor, spin) { // rect([40,30], chamfer=-5); // Example(2D): Negative-Rounded Rect // rect([40,30], rounding=-5); +// Example(2D): Combined Rounded-Chamfered Rect with corner flips +// rect(chamfer = 0.25*[0,1,-1,0], rounding=.25*[1,0,0,-1], corner_flip = true); // Example(2D): Default "box" Anchors // color("red") rect([40,30]); // rect([40,30], rounding=10) @@ -130,7 +133,7 @@ module square(size=1, center, anchor, spin) { // path = rect([40,30], chamfer=5, anchor=FRONT, spin=30); // stroke(path, closed=true); // move_copies(path) color("blue") circle(d=2,$fn=8); -module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, cswap = [0,0,0,0]) { +module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, corner_flip = false) { errchk = assert(in_list(atype, ["box", "perim"])); size = [for (c = force_list(size,2)) max(0,c)]; if (!all_positive(size)) { @@ -144,7 +147,7 @@ module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, c children(); } } else { - pts_over = rect(size=size, rounding=rounding, chamfer=chamfer, atype=atype, cswap = cswap, _return_override=true); + pts_over = rect(size=size, rounding=rounding, chamfer=chamfer, atype=atype, corner_flip = corner_flip, _return_override=true); pts = pts_over[0]; override = pts_over[1]; attachable(anchor, spin, two_d=true, size=size,override=override) { @@ -156,7 +159,7 @@ module rect(size=1, rounding=0, atype="box", chamfer=0, anchor=CENTER, spin=0, c -function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override, cswap = [0,0,0,0]) = +function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, _return_override, corner_flip = false) = assert(is_num(size) || is_vector(size,2)) assert(is_num(chamfer) || is_vector(chamfer,4)) assert(is_num(rounding) || is_vector(rounding,4)) @@ -164,6 +167,7 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, let( anchor=_force_anchor_2d(anchor), size = [for (c = force_list(size,2)) max(0,c)], + corner_flip = [for (c = force_list(corner_flip,4)) c ? true : false], chamfer = force_list(chamfer,4), rounding = force_list(rounding,4) ) @@ -201,24 +205,24 @@ function rect(size=1, rounding=0, chamfer=0, atype="box", anchor=CENTER, spin=0, qround = rounding[quad], cverts = quant(segs(abs(qinset)),4)/4, step = 90/cverts, - cp = v_mul(size/2 + (cswap[quad] ? (qinset > 0 ? 0 : 1) : -1)*[qinset,abs(qinset)], qpos), - + cp = v_mul(size/2 + (corner_flip[quad] ? (qinset > 0 ? 0 : 1) : -1)*[qinset,abs(qinset)], qpos), qpts = abs(qchamf) >= eps? [[0,abs(qinset)], [qinset,0]] : abs(qround) >= eps? [for (j=[0:1:cverts]) let(a=90-j*step) v_mul(polar_to_xy(abs(qinset),a),[sign(qinset),1])] : [[0,0]], - qfpts = [for (p=qpts) v_mul(p,cswap[quad] ? -qpos : qpos)], - qrpts = (cswap[quad] && qinset > 0 ? -1 : 1) * qpos.x*qpos.y < 0? reverse(qfpts) : qfpts, + qfpts = [for (p=qpts) v_mul(p,corner_flip[quad] ? -qpos : qpos)], + qrpts = (corner_flip[quad] && qinset > 0 ? -1 : 1) * qpos.x*qpos.y < 0? reverse(qfpts) : qfpts, cornerpt = atype=="box" || (qround==0 && qchamf==0) ? undef : qround<0 || qchamf<0 ? [[0,-qpos.y*min(qround,qchamf)]] : [for(seg=pair(qrpts)) let(isect=line_intersection(seg, [[0,0],qpos],SEGMENT,LINE)) if (is_def(isect) && isect!=seg[0]) isect] ) assert(is_undef(cornerpt) || len(cornerpt)==1,"Cannot find corner point to anchor") - [move(cp, p=qrpts), is_undef(cornerpt)? undef : move(cp,p=cornerpt[0])] + [move(cp, p=qrpts), is_undef(cornerpt)? undef : move(cp,p= + (min(chamfer[quad],rounding[quad])<0 && corner_flip[quad] ? [quadpos[quad].x*quadpos[quad].y*cornerpt[0].y, cornerpt[0].x] : cornerpt[0]))] ], path = deduplicate(flatten(column(corners,0)),closed=true), override = [for(i=[0:3]) let(quad=quadorder[i]) - if (is_def(corners[i][1])) [quadpos[quad], [corners[i][1], min(chamfer[quad],rounding[quad])<0 ? [quadpos[quad].x,0] : undef]]] + if (is_def(corners[i][1])) [quadpos[quad], [corners[i][1], min(chamfer[quad],rounding[quad])<0 ? (corner_flip[quad] ? [0, quadpos[quad].y] : [quadpos[quad].x, 0]) : undef]]] ) _return_override ? [reorient(anchor,spin, two_d=true, size=size, p=path, override=override), override] : reorient(anchor,spin, two_d=true, size=size, p=path, override=override);