Skip to content

Commit 6d60492

Browse files
Merge pull request #1625 from adrianVmariano/master
add rounding to wedge type arcs
2 parents cca0486 + 5a13c71 commit 6d60492

File tree

2 files changed

+91
-23
lines changed

2 files changed

+91
-23
lines changed

drawing.scad

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -694,9 +694,14 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
694694
// arc(...) [ATTACHMENTS];
695695
// Description:
696696
// If called as a function, returns a 2D or 3D path forming an arc. If `wedge` is true, the centerpoint of the arc appears as the first point in the result.
697-
// If called as a module, creates a 2D arc polygon or pie slice shape.
697+
// If called as a module, creates a 2D arc polygon or pie slice shape. Numerous methods are available to specify the arc.
698+
// .
699+
// The `rounding` parameter is permitted only when `wedge=true` and applies specified radius roundings at each of the corners, with `rounding[0]` giving
700+
// the rounding at the center point, and then the other two the two outer corners in the direction that the arc travels. If you don't need to control
701+
// the exact point count, you should use `$fs` and `$fa` to control the number of points on the roundings and arc. If you give `n` then each arc
702+
// section in your curve uses `n` points, so the total number of points is `n` times one plus the number of non-zero roundings you specified.
698703
// Arguments:
699-
// n = Number of vertices to form the arc curve from.
704+
// n = Number of vertices to use in the arc. If `wedge=true` you will get `n+1` points.
700705
// r = Radius of the arc.
701706
// angle = If a scalar, specifies the end angle in degrees (relative to start parameter). If a vector of two scalars, specifies start and end angles.
702707
// ---
@@ -712,6 +717,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
712717
// start = Start angle of arc. Default: 0
713718
// wedge = If true, include centerpoint `cp` in output to form pie slice shape. Default: false
714719
// endpoint = If false exclude the last point (function only). Default: true
720+
// rounding = Can set to a scalar or list of three rounding values to round the corners of an arc when wedge=true. Default: 0
715721
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). (Module only) Default: `CENTER`
716722
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). (Module only) Default: `0`
717723
// Examples(2D):
@@ -738,11 +744,16 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
738744
// path = arc(corner=pts, r=20);
739745
// stroke(pts, endcaps="arrow2");
740746
// stroke(path, endcap2="arrow2", color="blue");
741-
function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
747+
// Example(2D): Rounding the corners
748+
// $fs=.5; $fa=1;
749+
// arc(r=25, angle=[25,107], rounding=[6,5,7], wedge=true);
750+
// stroke(arc(r=25, angle=[25,107], wedge=true), color="red",closed=true, width=.5);
751+
752+
function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true, rounding) =
742753
assert(is_bool(endpoint))
743754
!endpoint ?
744755
assert(!wedge, "endpoint cannot be false if wedge is true")
745-
list_head(arc(u_add(n,1),r,angle,d,cp,points,corner,width,thickness,start,wedge,long,cw,ccw,true))
756+
list_head(arc(u_add(n,1),r,angle,d,cp,points,corner,width,thickness,start,wedge,long,cw,ccw,true,rounding))
746757
:
747758
assert(is_undef(start) || is_def(angle), "start requires angle")
748759
assert(is_undef(angle) || !any_defined([thickness,width,points,corner]), "Cannot give angle with points, corner, width or thickness")
@@ -754,7 +765,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
754765
assert(!any_defined([r,cp,points,angle,start]),"Conflicting or invalid parameters to arc")
755766
assert(width>0, "Width must be postive")
756767
assert(thickness>0, "Thickness must be positive")
757-
arc(n,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
768+
arc(n,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge,rounding=rounding)
758769
: is_def(angle)?
759770
let(
760771
parmok = !any_defined([points,width,thickness]) &&
@@ -770,6 +781,8 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
770781
assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
771782
assert(angle!=0, "Arc has zero length")
772783
assert(is_def(r) && r>0, "Arc radius invalid")
784+
is_def(rounding) ? assert(wedge,"rounding is only supportd with wedge=true") move(cp,zrot(start,_rounded_arc(r, rounding, angle, n)))
785+
:
773786
let(
774787
n = is_def(n) ? n : max(3, ceil(segs(r)*abs(angle)/360)),
775788
arcpoints = [for(i=[0:n-1]) let(theta = start + i*angle/(n-1)) r*[cos(theta),sin(theta)]+cp]
@@ -787,7 +800,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
787800
plane = [corner[2], corner[0], corner[1]],
788801
points2d = project_plane(plane, corner)
789802
)
790-
lift_plane(plane,arc(n,corner=points2d,wedge=wedge,r=r, d=d))
803+
lift_plane(plane,arc(n,corner=points2d,wedge=wedge,r=r, d=d,rounding=rounding))
791804
) :
792805
assert(is_path(corner) && len(corner) == 3)
793806
let(col = is_collinear(corner[0],corner[1],corner[2]))
@@ -802,9 +815,10 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
802815
theta_start = atan2(corner[0].y-cp.y, corner[0].x-cp.x),
803816
theta_end = atan2(corner[1].y-cp.y, corner[1].x-cp.x),
804817
angle = posmod(theta_end-theta_start, 360),
805-
arcpts = arc(n,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
818+
ang_range = dir ? [theta_start, theta_start+angle]
819+
: [theta_start+angle, theta_start]
806820
)
807-
dir ? arcpts : wedge ? reverse_polygon(arcpts) : reverse(arcpts)
821+
arc(n,cp=cp,r=r,angle=ang_range,wedge=wedge,rounding=rounding)
808822
: assert(is_def(points), "Arc not specified: must give points, angle, or width and thickness")
809823
assert(is_path(points,[2,3]),"Point list is invalid")
810824
// If arc is 3D, transform points to 2D and make a recursive call, then remap back to 3D
@@ -816,7 +830,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
816830
center2d = is_def(cp) ? project_plane(plane,cp) : undef,
817831
points2d = project_plane(plane, points)
818832
)
819-
lift_plane(plane,arc(n,cp=center2d,points=points2d,wedge=wedge,long=long))
833+
lift_plane(plane,arc(n,cp=center2d,points=points2d,wedge=wedge,long=long,rounding=rounding))
820834
: len(points)==2?
821835
// Arc defined by center plus two points, will have radius defined by center and points[0]
822836
// and extent defined by direction of point[1] from the center
@@ -839,7 +853,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
839853
dir*angle,
840854
sa = atan2(v1.y,v1.x)
841855
)
842-
arc(n,cp=cp,r=r,start=sa,angle=final_angle,wedge=wedge)
856+
arc(n,cp=cp,r=r,start=sa,angle=final_angle,wedge=wedge,rounding=rounding)
843857
: // Final case is arc passing through three points, starting at point[0] and ending at point[3]
844858
let(col = is_collinear(points[0],points[1],points[2]))
845859
assert(!col, "Collinear inputs do not define an arc")
@@ -854,29 +868,76 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
854868
angle = posmod(theta_end-theta_start, 360),
855869
// Specify endpoints exactly; skip those endpoints when producing arc points
856870
// Generating the whole arc and clipping ends is the easiest way to ensure that we
857-
// generate the proper number of points.
858-
arcpts = [ if (wedge) cp,
859-
points[0],
860-
each select(arc(n,cp=cp,r=r,start=theta_start,angle=angle),1,-2),
861-
points[1]
871+
// generate the proper number of points.
872+
ang_range = dir ? [theta_start, theta_start+angle]
873+
: [theta_start+angle, theta_start],
874+
arcpts = is_def(rounding)? arc(n,cp=cp,r=r,angle=ang_range,wedge=wedge,rounding=rounding)
875+
: [
876+
if (wedge) cp,
877+
points[dir ? 0 : 1],
878+
each select(arc(n,cp=cp,r=r,angle=ang_range),1,-2),
879+
points[dir ? 1 : 0]
862880
]
863-
864881
)
865-
dir ? arcpts
866-
: wedge ? reverse_polygon(arcpts) // Keep the centerpoint at position 0 in the list
867-
: reverse(arcpts);
882+
arcpts;
868883

869884

870-
module arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=false, anchor=CENTER, spin=0)
885+
module arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=false, rounding, anchor=CENTER, spin=0)
871886
{
872-
path = arc(n=n, r=r, angle=angle, d=d, cp=cp, points=points, corner=corner, width=width, thickness=thickness, start=start, wedge=wedge);
887+
path = arc(n=n, r=r, angle=angle, d=d, cp=cp, points=points, corner=corner, width=width, thickness=thickness, start=start, wedge=wedge, rounding=rounding);
873888
attachable(anchor,spin, two_d=true, path=path, extent=false) {
874889
polygon(path);
875890
children();
876891
}
877892
}
878893

879894

895+
function _rounded_arc(radius, rounding=0, angle, n) =
896+
assert(is_finite(angle) && angle>-360 && angle<360, "angle must be strictly between -360 and 360")
897+
assert(is_finite(rounding) || is_vector(rounding,3), "rounding must be a scalar or 3-vector")
898+
assert(all_nonnegative(rounding), "rounding values must be nonnegative")
899+
let(
900+
rounding = force_list(rounding,3),
901+
dir = sign(angle),
902+
903+
// inner_corner_radius = abs(angle)==180?0 : abs(angle)>180 ? -dir*rounding[0] : dir*rounding[0],
904+
inner_corner_radius = abs(angle)>180 ? -dir*rounding[0] : dir*rounding[0],
905+
arc1_opt_radius = radius - rounding[1],
906+
arc2_opt_radius = radius - rounding[2],
907+
check = assert(rounding[1]<arc1_opt_radius, "rounding[1] is too big to fit")
908+
assert(rounding[2]<arc2_opt_radius, "rounding[2] is too big to fit"),
909+
arc1_angle = asin(rounding[1]/arc1_opt_radius),
910+
arc2_angle = asin(rounding[2]/arc2_opt_radius),
911+
arc1_cut = radius - arc1_opt_radius*cos(arc1_angle),
912+
arc2_cut = radius - arc2_opt_radius*cos(arc2_angle),
913+
914+
radius_of_ctrpt = inner_corner_radius/sin(angle/2),
915+
radius_of_ctrpt_edge = radius_of_ctrpt*cos(angle/2),
916+
pt1 = polar_to_xy(r=arc1_opt_radius, theta=dir*arc1_angle),
917+
pt2 = polar_to_xy(r=radius_of_ctrpt, theta=0.5*angle),
918+
pt3 = polar_to_xy(r=arc2_opt_radius, theta=angle - dir*arc2_angle),
919+
920+
edge_gap1=radius-arc1_cut-radius_of_ctrpt_edge,
921+
edge_gap2=radius-arc2_cut-radius_of_ctrpt_edge
922+
)
923+
assert(arc1_angle + arc2_angle<=abs(angle), "Roundings are too large: they interfere with each other on the arc")
924+
assert(edge_gap1>=0, "Roundings are too large: center rounding (rounding[0]) interferes with first corner (rounding[1])")
925+
assert(edge_gap2>=0, "Roundings are too large: center rounding (rounding[0]) interferes with second corner (rounding[2])")
926+
[
927+
each if (rounding[0]>0 && abs(angle)!=180)
928+
arc(cp=pt2,
929+
points=[polar_to_xy(r=radius_of_ctrpt_edge, theta=angle), // origin corner curve
930+
polar_to_xy(r=radius_of_ctrpt_edge, theta=0)],
931+
endpoint=edge_gap1!=0,n=n)
932+
else repeat([0,0],rounding[0]>0 && abs(angle)==180 && is_def(n) ? n : 1),
933+
each if (rounding[1]>0) arc(r=rounding[1],cp=pt1,angle=[-90*dir,dir*arc1_angle],endpoint=dir*arc1_angle==angle,n=n), // first corner
934+
each if (arc1_angle+arc2_angle<abs(angle))
935+
arc(r=radius, angle=[dir*arc1_angle,angle - dir*arc2_angle], endpoint=rounding[2]==0,n=n), // main arc section
936+
each if (rounding[2]>0) arc(r=rounding[2],cp=pt3, angle=[angle-dir*arc2_angle, angle+dir*90],endpoint=edge_gap2!=0,n=n), // second corner
937+
];
938+
939+
940+
880941
// Function: catenary()
881942
// Synopsis: Returns a 2D Catenary chain or arch path.
882943
// SynTags: Path

tests/test_regions.scad

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,20 @@ test_point_in_region();
128128
module test_make_region(){
129129
pentagram = turtle(["move",100,"left",144], repeat=4);
130130
region1 = make_region(pentagram);
131-
assert_approx(region1,
131+
assert(are_regions_equal(region1,
132132
[[[0, 0], [38.196601125, 0], [30.9016994375, 22.451398829]], [[50,
133133
36.3271264003], [19.0983005625, 58.7785252292], [30.9016994375,
134134
22.451398829]], [[69.0983005625, 22.451398829], [50, 36.3271264003],
135135
[80.9016994375, 58.7785252292]], [[61.803398875, 3.5527136788e-15],
136136
[69.0983005625, 22.451398829], [100, 0]], [[38.196601125, 0],
137-
[61.803398875, 3.94430452611e-31], [50, -36.3271264003]]]);
137+
[61.803398875, 3.94430452611e-31], [50, -36.3271264003]]]));
138+
/*assert_approx(region1,
139+
[[[0, 0], [38.196601125, 0], [30.9016994375, 22.451398829]], [[50,
140+
36.3271264003], [19.0983005625, 58.7785252292], [30.9016994375,
141+
22.451398829]], [[69.0983005625, 22.451398829], [50, 36.3271264003],
142+
[80.9016994375, 58.7785252292]], [[61.803398875, 3.5527136788e-15],
143+
[69.0983005625, 22.451398829], [100, 0]], [[38.196601125, 0],
144+
[61.803398875, 3.94430452611e-31], [50, -36.3271264003]]]);*/
138145
region2 = make_region(pentagram,nonzero=true);
139146
assert_approx(region2,
140147
[[[0, 0], [38.196601125, 0], [50, -36.3271264003],

0 commit comments

Comments
 (0)