1515 "Cutout" ,
1616]
1717
18+ from math import ceil
1819from typing import Iterable , Sequence
1920
2021import numpy as np
@@ -133,18 +134,52 @@ def get_vertex_groups(self) -> np.ndarray:
133134
134135 return np .array (vertex_groups )
135136
136- def round_corners (self , radius : float = 0.5 ):
137+ def round_corners (
138+ self ,
139+ radius : float | list [float ] = 0.5 ,
140+ evenly_distribute_anchors : bool = False ,
141+ components_per_rounded_corner : int = 2 ,
142+ ):
137143 """Rounds off the corners of the :class:`Polygram`.
138144
139145 Parameters
140146 ----------
141147 radius
142148 The curvature of the corners of the :class:`Polygram`.
149+ evenly_distribute_anchors
150+ Break long line segments into proportionally-sized segments.
151+ components_per_rounded_corner
152+ The number of points used to represent the rounded corner curve.
143153
144154
145155 .. seealso::
146156 :class:`.~RoundedRectangle`
147157
158+ .. note::
159+ If `radius` is supplied as a single value, then the same radius
160+ will be applied to all corners. If `radius` is a list, then the
161+ individual values will be applied sequentially, with the first
162+ corner receiving `radius[0]`, the second corner receiving
163+ `radius[1]`, etc. The radius list will be repeated as necessary.
164+
165+ The `components_per_rounded_corner` value is provided so that the
166+ fidelity of the rounded corner may be fine-tuned as needed. 2 is
167+ an appropriate value for most shapes, however a larger value may be
168+ need if the rounded corner is particularly large. 2 is the minimum
169+ number allowed, representing the start and end of the curve. 3 will
170+ result in a start, middle, and end point, meaning 2 curves will be
171+ generated.
172+
173+ The option to `evenly_distribute_anchors` is provided so that the
174+ line segments (the part part of each line remaining after rounding
175+ off the corners) can be subdivided to a density similar to that of
176+ the average density of the rounded corners. This may be desirable
177+ in situations in which an even distribution of curves is desired
178+ for use in later transformation animations. Be aware, though, that
179+ enabling this option can result in an an object containing
180+ significantly more points than the original, especially when the
181+ rounded corner curves are small.
182+
148183 Examples
149184 --------
150185 .. manim:: PolygramRoundCorners
@@ -169,18 +204,28 @@ def construct(self):
169204
170205 for vertices in self .get_vertex_groups ():
171206 arcs = []
172- for v1 , v2 , v3 in adjacent_n_tuples (vertices , 3 ):
207+
208+ # Repeat the radius list as necessary in order to provide a radius
209+ # for each vertex.
210+ if isinstance (radius , (int , float )):
211+ radius_list = [radius ] * len (vertices )
212+ else :
213+ radius_list = radius * ceil (len (vertices ) / len (radius ))
214+
215+ for currentRadius , (v1 , v2 , v3 ) in zip (
216+ radius_list , adjacent_n_tuples (vertices , 3 )
217+ ):
173218 vect1 = v2 - v1
174219 vect2 = v3 - v2
175220 unit_vect1 = normalize (vect1 )
176221 unit_vect2 = normalize (vect2 )
177222
178223 angle = angle_between_vectors (vect1 , vect2 )
179224 # Negative radius gives concave curves
180- angle *= np .sign (radius )
225+ angle *= np .sign (currentRadius )
181226
182227 # Distance between vertex and start of the arc
183- cut_off_length = radius * np .tan (angle / 2 )
228+ cut_off_length = currentRadius * np .tan (angle / 2 )
184229
185230 # Determines counterclockwise vs. clockwise
186231 sign = np .sign (np .cross (vect1 , vect2 )[2 ])
@@ -189,9 +234,24 @@ def construct(self):
189234 v2 - unit_vect1 * cut_off_length ,
190235 v2 + unit_vect2 * cut_off_length ,
191236 angle = sign * angle ,
237+ num_components = components_per_rounded_corner ,
192238 )
193239 arcs .append (arc )
194240
241+ if evenly_distribute_anchors :
242+ # Determine the average length of each curve
243+ nonZeroLengthArcs = [arc for arc in arcs if len (arc .points ) > 4 ]
244+ if len (nonZeroLengthArcs ):
245+ totalArcLength = sum (
246+ [arc .get_arc_length () for arc in nonZeroLengthArcs ]
247+ )
248+ totalCurveCount = (
249+ sum ([len (arc .points ) for arc in nonZeroLengthArcs ]) / 4
250+ )
251+ averageLengthPerCurve = totalArcLength / totalCurveCount
252+ else :
253+ averageLengthPerCurve = 1
254+
195255 # To ensure that we loop through starting with last
196256 arcs = [arcs [- 1 ], * arcs [:- 1 ]]
197257 from manim .mobject .geometry .line import Line
@@ -201,10 +261,11 @@ def construct(self):
201261
202262 line = Line (arc1 .get_end (), arc2 .get_start ())
203263
204- # Make sure anchors are evenly distributed
205- len_ratio = line .get_length () / arc1 .get_arc_length ()
206-
207- line .insert_n_curves (int (arc1 .get_num_curves () * len_ratio ))
264+ # Make sure anchors are evenly distributed, if necessary
265+ if evenly_distribute_anchors :
266+ line .insert_n_curves (
267+ ceil (line .get_length () / averageLengthPerCurve )
268+ )
208269
209270 new_points .extend (line .points )
210271
@@ -633,7 +694,7 @@ def construct(self):
633694 self.add(rect_group)
634695 """
635696
636- def __init__ (self , corner_radius : float = 0.5 , ** kwargs ):
697+ def __init__ (self , corner_radius : float | list [ float ] = 0.5 , ** kwargs ):
637698 super ().__init__ (** kwargs )
638699 self .corner_radius = corner_radius
639700 self .round_corners (self .corner_radius )
0 commit comments