`CurvesAsSubmobjects` should keep attributes of `ParametricFunction`
Description of bug / unexpected behavior
When applying CurvesAsSubmobjects to a ParametricFunction, the attributes of ParametricFunction are no longer available. This means that the new curve cannot be used with many of the methods typically used with curves, like input_to_graph_point.
Expected behavior
CurvesAsSubmobjects should preserve the attributes of ParametricFunction, such as .function and .t_range.
How to reproduce the issue
Code for reproducing the problem
from manim import *
class CurveExample(Scene):
def construct(self):
axes = Axes(
x_range=[0, 6],
y_range=[-2, 2],
tips=False,
)
curve = axes.plot(
lambda x: 2 * np.sin(x), x_range=(0.0, 6, 0.001), color=BLUE
)
new_curve = CurvesAsSubmobjects(curve)
curve_dot = Dot(axes.input_to_graph_point(1, new_curve), color=ORANGE)
self.add(axes, new_curve, curve_dot)
Logs
Terminal output
Manim Community v0.17.2
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/cli/render/commands.py:115 in render │
│ │
│ 112 │ │ │ try: │
│ 113 │ │ │ │ with tempconfig({}): │
│ 114 │ │ │ │ │ scene = SceneClass() │
│ ❱ 115 │ │ │ │ │ scene.render() │
│ 116 │ │ │ except Exception: │
│ 117 │ │ │ │ error_console.print_exception() │
│ 118 │ │ │ │ sys.exit(1) │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/scene/scene.py:223 in render │
│ │
│ 220 │ │ """ │
│ 221 │ │ self.setup() │
│ 222 │ │ try: │
│ ❱ 223 │ │ │ self.construct() │
│ 224 │ │ except EndSceneEarlyException: │
│ 225 │ │ │ pass │
│ 226 │ │ except RerunSceneException as e: │
│ │
│ /home/alexlembcke/ManimDev/plot_colorscale.py:17 in construct │
│ │
│ 14 │ │ │
│ 15 │ │ new_curve = CurvesAsSubmobjects(curve) │
│ 16 │ │ │
│ ❱ 17 │ │ curve_dot = Dot(axes.input_to_graph_point(1, new_curve), color=ORANGE) │
│ 18 │ │ │
│ 19 │ │ self.add(axes, new_curve, curve_dot) │
│ 20 │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/mobject/graphing/coordinate_systems. │
│ py:991 in input_to_graph_point │
│ │
│ 988 │ │ if hasattr(graph, "underlying_function"): │
│ 989 │ │ │ return graph.function(x) │
│ 990 │ │ else: │
│ ❱ 991 │ │ │ alpha = binary_search( │
│ 992 │ │ │ │ function=lambda a: self.point_to_coords(graph.point_from_proportion(a))[ │
│ 993 │ │ │ │ │ 0 │
│ 994 │ │ │ │ ], │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/utils/simple_functions.py:63 in │
│ binary_search │
│ │
│ 60 │ mh = np.mean(np.array([lh, rh])) │
│ 61 │ while abs(rh - lh) > tolerance: │
│ 62 │ │ mh = np.mean(np.array([lh, rh])) │
│ ❱ 63 │ │ lx, mx, rx = (function(h) for h in (lh, mh, rh)) │
│ 64 │ │ if lx == target: │
│ 65 │ │ │ return lh │
│ 66 │ │ if rx == target: │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/utils/simple_functions.py:63 in │
│ <genexpr> │
│ │
│ 60 │ mh = np.mean(np.array([lh, rh])) │
│ 61 │ while abs(rh - lh) > tolerance: │
│ 62 │ │ mh = np.mean(np.array([lh, rh])) │
│ ❱ 63 │ │ lx, mx, rx = (function(h) for h in (lh, mh, rh)) │
│ 64 │ │ if lx == target: │
│ 65 │ │ │ return lh │
│ 66 │ │ if rx == target: │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/mobject/graphing/coordinate_systems. │
│ py:992 in <lambda> │
│ │
│ 989 │ │ │ return graph.function(x) │
│ 990 │ │ else: │
│ 991 │ │ │ alpha = binary_search( │
│ ❱ 992 │ │ │ │ function=lambda a: self.point_to_coords(graph.point_from_proportion(a))[ │
│ 993 │ │ │ │ │ 0 │
│ 994 │ │ │ │ ], │
│ 995 │ │ │ │ target=x, │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/mobject/types/vectorized_mobject.py: │
│ 1284 in point_from_proportion │
│ │
│ 1281 │ │ if alpha < 0 or alpha > 1: │
│ 1282 │ │ │ raise ValueError(f"Alpha {alpha} not between 0 and 1.") │
│ 1283 │ │ │
│ ❱ 1284 │ │ self.throw_error_if_no_points() │
│ 1285 │ │ if alpha == 1: │
│ 1286 │ │ │ return self.points[-1] │
│ 1287 │
│ │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/mobject/mobject.py:2731 in │
│ throw_error_if_no_points │
│ │
│ 2728 │ def throw_error_if_no_points(self): │
│ 2729 │ │ if self.has_no_points(): │
│ 2730 │ │ │ caller_name = sys._getframe(1).f_code.co_name │
│ ❱ 2731 │ │ │ raise Exception( │
│ 2732 │ │ │ │ f"Cannot call Mobject.{caller_name} for a Mobject with no points", │
│ 2733 │ │ │ ) │
│ 2734 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Exception: Cannot call Mobject.point_from_proportion for a Mobject with no points
System specifications
System Details
- OS: Ubuntu 22.10
- RAM: 32GB
- Python version (
python/py/python3 --version): Python 3.10.7
This might be pretty difficult to do, because conceptually they are two different kind of objects. There was some discussion at some point about rethinking the interaction with Mobjects and how they would be able to keep their actual interfaces.
Currently the only way to do this is just keeping the Parametric function around and then using the function of that to position other things but not displaying it.