Skip to content

Commit 80795d8

Browse files
coreyp1behackl
andauthored
Added support for individualized radius values in :meth:.Polygram.round_corners (#3155)
* feat(Polygram.round_corners): add support for individualized radius values * Update manim/mobject/geometry/polygram.py Remove type hints from the docstring of round_corners() Co-authored-by: Benjamin Hackl <[email protected]> * Update rounded_corners() so that integer values are accepted --------- Co-authored-by: Benjamin Hackl <[email protected]>
1 parent 1edcd99 commit 80795d8

File tree

2 files changed

+70
-9
lines changed

2 files changed

+70
-9
lines changed

manim/mobject/geometry/polygram.py

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"Cutout",
1616
]
1717

18+
from math import ceil
1819
from typing import Iterable, Sequence
1920

2021
import 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)
Binary file not shown.

0 commit comments

Comments
 (0)