@@ -694,9 +694,14 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
694
694
// arc(...) [ATTACHMENTS];
695
695
// Description:
696
696
// 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.
698
703
// 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.
700
705
// r = Radius of the arc.
701
706
// 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.
702
707
// ---
@@ -712,6 +717,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
712
717
// start = Start angle of arc. Default: 0
713
718
// wedge = If true, include centerpoint `cp` in output to form pie slice shape. Default: false
714
719
// 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
715
721
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). (Module only) Default: `CENTER`
716
722
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). (Module only) Default: `0`
717
723
// Examples(2D):
@@ -738,11 +744,16 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false, fit=true, round
738
744
// path = arc(corner=pts, r=20);
739
745
// stroke(pts, endcaps="arrow2");
740
746
// 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) =
742
753
assert(is_bool(endpoint))
743
754
! endpoint ?
744
755
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 ))
746
757
:
747
758
assert(is_undef(start) || is_def(angle), "start requires angle" )
748
759
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=
754
765
assert(! any_defined([r,cp,points,angle,start]),"Conflicting or invalid parameters to arc" )
755
766
assert(width> 0 , "Width must be postive" )
756
767
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 )
758
769
: is_def(angle)?
759
770
let(
760
771
parmok = ! any_defined([points,width,thickness]) &&
@@ -770,6 +781,8 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
770
781
assert(is_vector(cp,2 ),"Centerpoint must be a 2d vector" )
771
782
assert(angle!= 0 , "Arc has zero length" )
772
783
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
+ :
773
786
let(
774
787
n = is_def(n) ? n : max (3 , ceil (segs(r)* abs (angle)/360 )),
775
788
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=
787
800
plane = [corner[2 ], corner[0 ], corner[1 ]],
788
801
points2d = project_plane(plane, corner)
789
802
)
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 ))
791
804
) :
792
805
assert(is_path(corner) && len(corner) == 3 )
793
806
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=
802
815
theta_start = atan2 (corner[0 ].y- cp.y, corner[0 ].x- cp.x),
803
816
theta_end = atan2 (corner[1 ].y- cp.y, corner[1 ].x- cp.x),
804
817
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]
806
820
)
807
- dir ? arcpts : wedge ? reverse_polygon(arcpts) : reverse(arcpts )
821
+ arc(n,cp = cp,r = r,angle = ang_range,wedge = wedge,rounding = rounding )
808
822
: assert(is_def(points), "Arc not specified: must give points, angle, or width and thickness" )
809
823
assert(is_path(points,[2 ,3 ]),"Point list is invalid" )
810
824
// 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=
816
830
center2d = is_def(cp) ? project_plane(plane,cp) : undef,
817
831
points2d = project_plane(plane, points)
818
832
)
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 ))
820
834
: len(points)== 2 ?
821
835
// Arc defined by center plus two points, will have radius defined by center and points[0]
822
836
// 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=
839
853
dir* angle,
840
854
sa = atan2 (v1.y,v1.x)
841
855
)
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 )
843
857
: // Final case is arc passing through three points, starting at point[0] and ending at point[3]
844
858
let(col = is_collinear(points[0 ],points[1 ],points[2 ]))
845
859
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=
854
868
angle = posmod(theta_end- theta_start, 360 ),
855
869
// Specify endpoints exactly; skip those endpoints when producing arc points
856
870
// 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 ]
862
880
]
863
-
864
881
)
865
- dir ? arcpts
866
- : wedge ? reverse_polygon(arcpts) // Keep the centerpoint at position 0 in the list
867
- : reverse(arcpts);
882
+ arcpts;
868
883
869
884
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)
871
886
{
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 );
873
888
attachable(anchor,spin, two_d= true , path= path, extent= false ) {
874
889
polygon(path);
875
890
children();
876
891
}
877
892
}
878
893
879
894
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
+
880
941
// Function: catenary()
881
942
// Synopsis: Returns a 2D Catenary chain or arch path.
882
943
// SynTags: Path
0 commit comments