I am trying to create a rose. I am able to complete it with the following code, but for some reason, I am unable to fill the rose with color. As you can see, I am using set_fill() method to fill the color with BLUE.But somehow, no fill is received at my end. I am unable to solve this problem.
from manim import *
import numpy as np
class Rose(Scene):
def func(self, t):
a, k = 2, 10
x = a*np.cos(k*t)*np.cos(t)
y = a*np.cos(k*t)*np.sin(t)
return np.array((x, y, 0))
def construct(self):
func = CurvesAsSubmobjects(ParametricFunction(self.func,
t_range=np.array([0, TAU])))
func = func.set_color(BLUE).set_stroke(width=1).set_fill(BLUE,opacity=0.5)
self.play(Create(func.scale(1)), run_time=8)
self.wait(1)
I am getting an output but the rose is not filled with the BLUE color. Thanks for the help. Manim version: v0.17.3 and python 3.11
The problem is the use of CurvesAsSubmobjects()
. This class divides the continuous curve generated by ParametricFunction()
into a ton (around 600 actually) tiny objects.
When you set the fill color, you are setting it for each of these tiny objects, not for the "global" curve. Each of these tiny objects is indeed so thin, that its area is zero and the fill color cannot be seen.
So the solution is to remove the CurvesAsSubmobjects()
call. Why did you include it? My guess is that you did that to avoid a mysterious exception that you'll get otherwise:
TypeError: cannot pickle '_thread.lock' object
This error happens if you try to animate directly the creation of the ParametricFunction
, and disappears if you use CurvesAsSubmobjects()
.
However, the true cause of that exception is that the function you are passing to ParametricFunction()
is a method of the Scene
, because you defined it so:
class Rose(Scene):
def func(self, t):
a, k = 2, 10
x = a*np.cos(k*t)*np.cos(t)
y = a*np.cos(k*t)*np.sin(t)
return np.array((x, y, 0))
So, when you call:
ParametricFunction(self.func, t_range=np.array([0, TAU]))
you are passing a method which is part of the Scene. This causes problems when internally Manim tries to perform deepcopies of the object (which happens during animations). The problem is that, due to the way you defined the function, it has to deepcopy also the Scene, and this cannot be done because the scene contains a logger. The reason is very technical and obscure.
But you don't need to have func()
has a method of the scene. It can be an external function, or even an internal function defined inside construct()
(this does not makes it a method, because it has not relation with self
).
So the solution to your problem is:
func()
so that it is not a method of the SceneCurvesAsSubmobjects()
I.e: your code should be:
class Test(Scene):
def construct(self):
def f(t):
a, k = 2, 10
x = a*np.cos(k*t)*np.cos(t)
y = a*np.cos(k*t)*np.sin(t)
return np.array((x, y, 0))
func = ParametricFunction(f, t_range=np.array([0, TAU, 0.1]))
func = func.set_color(BLUE).set_stroke(width=1).set_fill(BLUE,opacity=0.5)
self.play(Create(func, run_time=8, rate_func=linear))
self.wait()
This works, but perhaps not how you desired:
I think that you wanted to use Write()
instead of Create()
, since Write()
draws first the contour and then fades in the filling:
Create()
can also work nicely if you start the drawing at the center of the rose, instead of at one extreme. You can do so by adding PI/2
to the cos() component:
class Test(Scene):
def construct(self):
def f(t):
a, k = 2, 10
x = a*np.cos(k*t+PI/2)*np.cos(t)
y = a*np.cos(k*t+PI/2)*np.sin(t)
return np.array((x, y, 0))
func = ParametricFunction(f, t_range=np.array([0, TAU, 0.1]))
func = func.set_color(BLUE).set_stroke(width=1).set_fill(BLUE, opacity=0.5)
self.play(Create(func, run_time=8, rate_func=linear))
self.wait()
Results in: