manim
manim copied to clipboard
Arrow3D: get_start() and get_end() methods both return np.array([0,0,0])
Description of bug / unexpected behavior
Arrow3D doesn't appear to return the correct start and/or end points.
Instead of seeing the start and end points I used to create the 3d error, I get 0,0,0 for both.
Expected behavior
Given the sample code I pasted below, I would expect to see:
array([2., 2., 2.]) array([0., 0., 0.])
Instead I get:
array([0., 0., 0.]) array([0., 0., 0.])
How to reproduce the issue
Code for reproducing the problem
vectorStart = ORIGIN
vectorEnd = np.array([2, 2, 2])
vector = Arrow3D(
start = vectorStart,
end = vectorEnd,
color = BLUE_D,
thickness = 0.015, # thickness of shaft
base_radius = 0.06 # radius of the base of the conical arrow
)
# Draw the vector
self.play(Create(vector))
from pprint import pprint
pprint(vector.get_end())
pprint(vector.get_start())
import sys
sys.exit(1)
Additional media files
Images/GIFs
Logs
Terminal output
PASTE HERE OR PROVIDE LINK TO https://pastebin.com/ OR SIMILAR
System specifications
System Details
- OS (with version, e.g., Windows 10 v2004 or macOS 10.15 (Catalina)): Ubuntu 22.04
- RAM: 24GB
- Python version (
python/py/python3 --version): 3.10.12 - Installed modules (provide output from
pip list):
Package Version
----------------- -----------
av 13.1.0
beautifulsoup4 4.13.4
click 8.2.0
cloup 3.0.7
decorator 5.2.1
glcontext 3.0.0
isosurfaces 0.1.2
manim 0.19.0
ManimPango 0.6.0
mapbox_earcut 1.0.3
markdown-it-py 3.0.0
mdurl 0.1.2
moderngl 5.12.0
moderngl-window 3.1.1
networkx 3.4.2
numpy 2.2.6
pillow 11.2.1
pip 22.0.2
pycairo 1.28.0
pydub 0.25.1
pyglet 2.1.6
pyglm 2.8.2
Pygments 2.19.1
rich 14.0.0
scipy 1.15.3
screeninfo 0.8.1
setuptools 68.1.2
skia-pathops 0.8.0.post2
soupsieve 2.7
srt 3.5.3
svgelements 1.9.6
tqdm 4.67.1
typing_extensions 4.13.2
watchdog 6.0.0
LaTeX details
- LaTeX distribution (e.g. TeX Live 2020):
- Installed LaTeX packages:
Additional comments
I'm not sure if I've misunderstood how this is supposed to work, but I assumed it would return the start and end points of the line that was used to construct the arrow.
to be fair, .get_start() does seem to return the correct coordinates, but there is a problem with .get_end() as well as .end_point as one of the object's properties. But this is only giving he wrong results after a Create() animation, not after just adding the arrow to the scene, nor after a FadeIn() (as a workaround)
class VectorFieldVisualization(ThreeDScene):
def construct(self):
vectorStart = np.array([-2, -2, -2])
vectorEnd = np.array([2, 2, 2])
vector = Arrow3D(
start = vectorStart,
end = vectorEnd,
color = BLUE_D,
thickness = 0.015, # thickness of shaft
base_radius = 0.06, # radius of the base of the conical arrow
)
v2 = vector.copy()
# Draw the vector
self.add(vector)
self.play(Create(v2))
print("vector")
print(vector.get_start())
print(vector.start)
print(vector.get_end())
print(vector.end_point.get_center())
print("v2")
print(v2.get_start())
print(v2.start)
print(v2.get_end())
print(v2.end_point.get_center())
results in
vector
[-2 -2 -2]
[-2 -2 -2]
[2. 2. 2.]
[2. 2. 2.]
v2
[-2 -2 -2]
[-2 -2 -2]
[0. 0. 0.]
[0. 0. 0.]
The FadeIn animation does not corrupt the values:
self.add(vector)
self.play(FadeIn(v2))
vector
[-2 -2 -2]
[-2 -2 -2]
[2. 2. 2.]
[2. 2. 2.]
v2
[-2 -2 -2]
[-2 -2 -2]
[2. 2. 2.]
[2. 2. 2.]
@uwezi , thank you for testing and clarifying that get_start() works as expected. I've been digging a bit more, and the plot thickens...
If I examine arrow.end rather than arrow.get_end(), I see that the value of arrow.end is preserved after a call to Create(). However, the values of arrow.end vs. arrow.get_end() are a little different. I haven't tested .start and .get_start() to see if there's a difference between those.
Where arrow.get_end() (before calling Create) would return np.array([2, 2, 2]), examining arrow.end both before and after calling Create gives me np.array([1.82679492, 1.82679492, 1.82679492]), which is kinda-sorta close but a little off. Is this another bug, or is there a reason for the difference that I didn't see in the documentation? It seems from the documentation like arrow.end should also contain np.array([2, 2, 2]).
Perhaps this is the length of the line minus the length of the arrow tip?
EDIT: The magnitude of np.array([1.82679492, 1.82679492, 1.82679492]) is the magnitude of the Arrow3D length minus the height of the conical tip, which was set to the default 0.3.
The plot thickens further.
Before calling create, vector.cone.height for the test case vector I used in the issue description gives me 0.3422797750859028. After calling Create(), the same property gives me 2.0. The value of vector.cone.end is also corrupted by a call to Create(), and I assume it's this value that Arrow3D's get_end() method ultimately returns.
Based on my previous comments, I have an ugly but functional workaround until this gets fixed.
# This is the default value of the conical height passed into the Arrow3D constructor,
# but you would change this if you set it to a different value
conicalTipHeight = 0.3
# This will return an incorrect value after Create(arrow3d) is called.
# arrowEnd = arrow3d.get_end()
# Do this instead
arrowEnd = np.array([
arrow3d.end[0] + (arrow3d.direction[0] * conicalTipHeight),
arrow3d.end[1] + (arrow3d.direction[1] * conicalTipHeight),
arrow3d.end[2] + (arrow3d.direction[2] * conicalTipHeight),
])
The idea behind this:
You add to the components of arrow3d.end the components of the conical tip height. This should, in theory, give you a coordinate that's equal to arrow3d.get_end(), minus miniscule differences due to floating point imprecision.
Of course, the values of height and end in vector.cone are also corrupted after calling Create(), so you really have no choice that I can see right now but to manually set this value.
EDIT: This doesn't appear to work if the Arrow3D has been moved / rotated, so this won't work in an updater. I'm trying to think of another solution.
EDIT 2: I found something that works with updaters, but it's even uglier than my last attempt.
# Get all points associated with all children of the Arrow3D
allPts = arrow3d.get_all_points()
# Get the point that's farthest from the start
arrowEnd = allPts[np.argmax(np.linalg.norm(allPts - arrow3d.get_start(), axis = 1))]
There might be another workaround that's cleaner, but this did work inside of an updater and I was able to follow the end point around as a vector rotated around inside a scene I was writing. Maybe someone else can offer a better workaround.
I have looked at this issue, and have managed to reduce the example that triggers the problem.
The basic observation is that the Arrow3D structure is based on the Line3D class with two additional elements, a cone and an end_point. The get_end method from Arrow3D simply returns the center of the end_point.
Therefore the main issue can be reduced to the VectorizedPoint class instead of the Arrow3D class.
I have tracked the code location where the coordinates are altered to be in the clear_points method in the VMObject class.
That in this case is called from line 1918 in vectorized_mobject.py.
from manim import *
class VectorFieldVisualization(ThreeDScene):
def construct(self):
# Create a vectorized point and get its coordinates.
point = VectorizedPoint(DOWN + LEFT)
print(point.get_center())
print(point.points)
# Creating the vectorized point resets the end_point to [0, 0, 0].
# This is caused by the clear_points method in the VMObject class.
# Which is called from line 1918 in vectorized_mobject.py.
self.play(Create(point))
# The coordinates of the vectorized point has now been changed.
print(point.get_center())
print(point.points)
The issue was fixed by PR #4320.